Remarque : quatre statuts = le minimum syndical. \cite[p. 297]{restful_web_apis} :
\begin{enumerate}
\item
\textbf{200 (OK)}.
Tout va bien.
Le document qui se trouve dans le corps de la réponse, s'il y en a un, est la représentation d'une ressource.
\item
\textbf{301 (Moved Permanently)}.
Reçu lorsque la ressource n'est plus disponible à cette URI.
\item
\textbf{400 (Bad Request)}.
Indique qu'il y a eu un problème côté client.
Le document qui se trouve dans le corps de la réponse, s'il existe, est un message d'erreur.
Avec un peu de chance, le client a la possibilité d'interpréter ce message d'erreur, afin de corriger le problème.
\item
\textbf{500 (Internal Server Error)}.
Il y a un problème côté serveur. Le document présent dans le corps de la réponse, toujours s'il existe, indique le problème.
Comme celui-ci se situe au niveau du serveur, le client ne pourra rien faire pour le résoudre.
\end{enumerate}
Au niveau du modèle, nous allons partir de quelque chose de très simple: des personnes, des contrats, des types de contrats, et un service d'affectation.
Quelque chose comme ceci:
@ -361,3 +382,15 @@ Une solution plus facile que les sérializeurs de DRF consistera à
\section{OData}
...
\section{Bonnes pratiques}
\subsection{Authentification}
\subsection{Validation des données}
\subsection{Utilisation d'une API Gateway}
\subsection{Rate limiting}
\subsection{Partage des données nécessaires uniquement}
New code should never go live without first being audited for correctness and security. \cite{django_for_startup_founders}
\textit{New code should never go live without first being audited for correctness and security}. \cite{django_for_startup_founders}
\end{quote}
Les processus d'intégration continue et de déploiement continu (respectivement, CI/CD - Continuous Integration / Continuous Deployment) permettent d'automatiser les étapes de vérifications, autrement réalisées par un être humain, voire manuellement, par des automates réagissant à des évènements particuliers - typiquement, une nouvelle version du code, un nouveau \textit{commit}, la mise à jour d'un des composants, \ldots
Les processus d'intégration continue \footnote{CI - Continuous Integration} et de déploiement continu \footnote{CD - Continuous Deployment} permettent d'automatiser des étapes de vérifications, initialement réalisées par un être humain, par des automates réagissant à des évènements particuliers: typiquement, une nouvelle version du code, un nouveau \textit{commit}, la mise à jour d'un des composants, \ldots
\section{Intégration continue}
@ -25,11 +25,10 @@ matrix:
- 3.8
pipeline:
static_checks:
image: acidrain/python-poetry:${PYTHON_VERSION}
lint:
image: python:${PYTHON_VERSION}
commands:
- python -V
- poetry -V
- pip3 install poetry
- poetry install
- poetry run flake8 src/
- poetry run pylint src/
@ -37,18 +36,15 @@ pipeline:
- poetry run mypy .
unit_tests:
image: acidrain/python-poetry:${PYTHON_VERSION}
image: python:${PYTHON_VERSION}
commands:
- python -V
- poetry -V
- poetry install
- pip3 install poetry
- poetry config virtualenvs.create false
- poetry run pytest --cov-report term-missing tests/unit/
\end{minted}
\section{Déploiement continu}
Le déploiement continu est une extension de l'intégration continue, qui est jusqu'au-boutiste à s'occuper lui-même de mettre à disposition une nouvelle version, sous réserve qu'elle ait passé les différentes étapes du pipeline.
C'est ainsi qu'Amazon ou certaines grandes \footnote{ie. envahissantes} entreprises publient jusqu'à plusieurs miliers de nouvelles fonctionnalités \textbf{par jour}.
C'est ainsi qu'Amazon ou certaines grandes \footnote{ie. \textit{"envahissantes"}} entreprises publient jusqu'à plusieurs miliers de nouvelles fonctionnalités \textbf{par jour}.
@ -95,14 +95,23 @@ Ce dépôt n'est pas uniquement destiné à hébergé le code source, mais égal
Chaque installation ou configuration doit être toujours réalisée de la même manière, et doit pouvoir être répétée quel que soit l'environnement cible.
Ceci permet d'éviter que l'application n'utilise une dépendance qui ne soit installée que sur l'un des sytèmes de développement, et qu'elle soit difficile, voire impossible, à réinstaller sur un autre environnement.
\begin{memorizebox}
Chaque dépendance devra être déclarée dans un fichier présent au niveau de la base de code.
Lors de la création d'un nouvel environnement vierge, il suffira d'utiliser ce fichier comme paramètre afin d'installer les prérequis au bon fonctionnement de notre application, afin d'assurer une reproductibilité quasi parfaite de l'environnement d'exécution.
\end{memorizebox}
Il est important de bien "épingler" les versions liées aux dépendances de l'application.
Il est important de bien "épingler" la version liée à chacune des dépendances de l'application.
Ceci peut éviter des effets de bord comme une nouvelle version d'une librairie dans laquelle un bug aurait pu avoir été introduit.\footnote{Le paquet PyLint dépend par exemple d'Astroid; \href{https://github.com/PyCQA/pylint-django/issues/343}{en janvier 2022}, ce dernier a été mis à jour sans respecter le principe de versions sémantiques et introduisant une régression. PyLint spécifiait que sa dépendance avec Astroid devait respecter une version ~2.9. Lors de sa mise à jour en 2.9.1, Astroid a introduit un changement majeur, qui faisait planter Pylint. L'épinglage explicite aurait pu éviter ceci.}.
Dans le cas de Python, la déclaration explicite et l'épinglage pourront notamment être réalisés au travers de \href{https://pypi.org/project/pip/}{PIP} ou \href{https://python-poetry.org/}{Poetry}.
La majorité des langages modernes proposent des mécanismes similaires (\href{https://rubygems.org/}{Gem} pour Ruby, \href{https://www.npmjs.com/}{NPM} pour NodeJS, \ldots)
La majorité des langages modernes proposent des mécanismes similaires :
\begin{itemize}
\item\href{https://rubygems.org/}{Gem} pour Ruby,
\item\href{https://www.npmjs.com/}{NPM} pour NodeJS,
\item\href{Maven}{https://maven.apache.org/} pour Java,
\item\ldots
\end{itemize}
\section{Configuration applicative}
@ -118,15 +127,48 @@ que:
En pratique, toute information susceptible d'évoluer ou de changer (un seuil, une ressource externe, un couple utilisateur/mot de passe, \ldots) doit se trouver dans un fichier ou dans une variable d'environnement, et doit être facilement modifiable.
Ceci permet de paramétrer facilement un environnement (par exemple, un container), simplement en modifiant une variable de configuration qui spécifierait la base de données sur laquelle l'application devra se connecter.
En pratique, avec du code Python/Django, nous pourrions utiliser la libraririe \href{django-environ}{https://pypi.org/project/django-environ/}, qui permet de gérer facilement ce fichier de configuration :
Toute clé de configuration (nom du serveur de base de données, adresse d'un service Web externe, clé d'API pour l'interrogation d'une ressource, \ldots) sera définie directement au niveau de l'hôte - à aucun moment, nous ne devons trouver un mot de passe en clair dans le dépôt source ou une valeur susceptible d'évoluer, écrite en dur dans le code. \footnote{Ainsi, nous pourrions faire une \href{https://github.com/search?q=filenamefilename:.env DB_USERNAME&type=Code}{recherche sur Github} pour retrouver certaines variables d'environnement qui auraient été laissées en dur dans le code source de certains projets. Le \href{https://github.com/techgaun/github-dorks}{dépôt suivant} liste quelques idées de variables à rechercher\ldots}.
DEBUG = env('DEBUG')
SECRET_KEY = env('SECRET_KEY')
Au moment de développer une nouvelle fonctionnalité, réfléchissez si
l'un des paramètres utilisés risquerait de subir une modification ou s'il concerne un principe de sécurité: ce composant peut concerner une nouvelle chaîne de connexion, un point de terminaison nécessaire à télécharger des données officielles ou un chemin vers un répertoire partagé pour y déposer un fichier.
DATABASES = {
# Parse database connection url strings
# like psql://user:pass@127.0.0.1:8458/db
'default': env.db(),
}
Le risque est de se retrouver avec une liste colossale de paramètres; pensez à leur assigner une variable par défaut.
Par exemple, Gitea expose la liste suivante de paramètres \footnote{\url{https://docs.gitea.io/en-us/config-cheat-sheet/}}; il serait impossible de tous les configurer un à un avant de pouvoir démarrer une instance.
CACHES = {
'default': env.cache(),
}
\end{minted}
\end{listing}
Il suffira ensuite d'appeler ces variables grâce à \texttt{from django.conf import settings} pour récupérer la valeur qui aura été configurée.
Ceci permet de paramétrer facilement n'importe quel environnement, simplement en modifiant une variable de ce fichier de configuration.
Toute clé de configuration, qu'il s'agisse d'une chaîne de connexion vers une base de données, l'adresse d'un service Web externe, une clé d'API pour l'interrogation d'une ressource, d'un chemin vers un fichier à interprêter ou à transférer, \ldots, ou n'importe quelle valeur susceptible d'évoluer, ne doit se trouver écrite en dur dans le code.
\begin{dangerbox}
Il est important de ne surtout pas ajouter ce fichier \texttt{.env} dans le dépôt de sources: à aucun moment, nous ne devons y trouver de mot de passe ou d'information confidentielle en clair. \footnote{Ainsi, nous pourrions faire une \href{https://github.com/search?q=filenamefilename:.env DB_USERNAME&type=Code}{recherche sur Github} pour retrouver certaines variables d'environnement qui auraient été laissées en dur dans le code source de certains projets. Le \href{https://github.com/techgaun/github-dorks}{dépôt suivant} liste quelques idées de variables à rechercher\ldots}.
\end{dangerbox}
Au moment de développer une nouvelle fonctionnalité, réfléchissez si l'un des paramètres utilisés risquerait de subir une modification ou s'il concerne un principe de sécurité.
Le risque de se retrouver avec une liste colossale de paramètres n'est cependant pas négligeable; pensez à leur assigner une variable par défaut \footnote{Par exemple, Gitea expose \href{la liste suivante de paramètres disponibles}{https://docs.gitea.io/en-us/config-cheat-sheet/}; il serait impossible d'utiliser cette plateforme si chacun d'entre eux devait obligatoirement être configuré avant de pouvoir démarrer une instance.}
\section{Ressources externes}
@ -142,7 +184,7 @@ Ces ressources sont donc spécifiés grâce à des variables d'environnement, et
\caption{Gestion des ressources attachées}
\end{graphic}
Nous serons ainsi ravis de simplement pouvoir modifier la chaîne de connexion \texttt{sqlite:////tmp/my-tmp-sqlite.db} en \texttt{psql://user:pass@127.0.0.1:8458/db} lorsque ce sera nécessaire, sans avoir à recompiler ou redéployer les modifications.
Nous serons ainsi ravis de pouvoir simplement modifier la chaîne de connexion \texttt{sqlite:////tmp/my-tmp-sqlite.db} en \texttt{psql://user:pass@127.0.0.1:8458/db} lorsque ce sera nécessaire, sans avoir à recompiler ou redéployer les modifications.
\section{Séparation des phases de construction}
@ -150,30 +192,41 @@ Nous serons ainsi ravis de simplement pouvoir modifier la chaîne de connexion \
\item
La \textbf{construction} (\emph{build}) convertit un code source en un ensemble de fichiers exécutables, associé à une version et à une transaction dans le système de gestion de sources.
\item
La \textbf{mise à disposition} (\emph{release}) associe cet ensemble à une configuration prête à être exécutée,
La \textbf{mise à disposition} (\emph{release}) associe cet ensemble à une configuration prête à être exécutée sur un environnement désigné,
\item
La phase d'\textbf{exécution} (\emph{run}) démarre les processus nécessaires au bon fonctionnement de l'application.
\caption{Séparation des phases de construction, mise à disposition et exécution}
\end{graphic}
Parmi les solutions possibles, nous pourrions nous pourrions nous baser
sur les \emph{releases} de Gitea, sur un serveur d'artefacts (\href{https://fr.wikipedia.org/wiki/Capistrano_(logiciel)}{Capistrano}), voire directement au niveau de forge logicielle (Gitea, Github, Gitlab, \ldots).
\begin{memorizebox}
Dans le cas de Python, la phase de construction (\textit{Build}) correspondra plutôt à une phase d'\textit{empaquettage} (\textit{packaging}).
Une fois préparée, la librairie ou l'application pourra être publiée sur \url{pypi.org} ou un dépôt géré en interne.
La phase de \emph{release} revient à associer une version spécifiquement empaquêtée pour l'associer à une configuration particulière.
\end{memorizebox}
\section{Mémoire des processus d'exécution}
Toute information stockée en mémoire ou sur disque ne doit pas altérer le comportement futur de l'application, par exemple après un redémarrage non souhaité.
Dit autrement, l'exécution de l'application ne doit pas dépendre de la présence d'une information locale à son environnement d'exécution.
Pratiquement, si l'application devait rencontrer un problème - par exemple suite à un problème matériel -, il est nécessaire qu'elle puisse redémarrer rapidement, éventuellement en étant déployée sur un autre serveur.
Toute information stockée physiquement durant son exécution sur le premier hôte, sera perdue lors de son déploiement ultérieur à un autre endroit.
\begin{memorizebox}
L'exécution de l'application ne doit pas dépendre de la présence d'une information locale à son environnement d'exécution.
\end{memorizebox}
En pratique, si l'application devait rencontrer un problème - par exemple suite à un problème matériel, une coupure réseau, \ldots -, il est nécessaire qu'elle puisse redémarrer rapidement, éventuellement en étant déployée sur un autre serveur.
Toute information stockée physiquement sera alors perdue, puisque le contexte d'exécution aura été déplacée à un autre endroit.
Lors de l'initialisation ou de la réinitialisation d'une application, la solution consiste à jouer sur les variables d'environnement (cf. \#3) et sur les informations que l'on pourra trouver au niveau des ressources attachées (cf \#4), afin de faire en sorte que les informations et données primordiales puissent être récupérées ou reconstruites, et donc réutilisées, sans altérer le comportement attendu.
Ceci joue également sur les possibilités de mise à l'échelle: en ajoutant un nouveau serveur d'exécution, si une donnée indispensable à son fonctionnement devait se trouver sur la seule machine où elle est actuellement exécutée, elle en sera inconnue.
\caption{La mémoire des processus d'exécution doit rester accessible, même après leur disparition}
\end{graphic}
-> Ajouter un schéma.
Ceci joue énormément sur les possibilités de mise à l'échelle: lors de l'ajout d'un nouveau serveur, il est indispensable qu'il puisse accéder à l'ensemble des informations nécessaires au bon fonctionnement de l'application.
\section{Liaison des ports}
@ -189,34 +242,37 @@ Le serveur (= l'hôte) choisit d'appliquer une correspondance entre "son" port 4
\section{Connaissance et confiance des processus systèmes}
Comme décrit plus haut (cf. \#6), l'application doit utiliser des processus \emph{stateless} (sans état).
Nous pouvons créer et utiliser des processus supplémentaires pour tenir plus facilement une lourde charge, ou dédier des particuliers pour certaines tâches:
Comme décrit plus haut (cf. \#6), l'application doit utiliser des processus sans état (\textit{stateless}), c'est-à-dire qu'aucune information ne doit être stockée localement ou ne doit dépendre d'une information précédemment acquise et accessible uniquement à un processus en particulier.
Nous pouvons créer et utiliser des processus supplémentaires pour tenir plus facilement une lourde charge ou dédier des particuliers pour certaines tâches:
\begin{itemize}
\item Requêtes HTTP \emph{via} des processus Web;
\item\emph{long-running} jobs pour des processus asynchrones,
\itemJobs asynchrones pour des tâches à effectuer en arrière-plan,
\item\ldots
\end{itemize}
Si l'un des concepts dont vous avez besoin existe sur l'hôte hébergeant l'application, ne vous fatiguez pas: utilisez le et ne réinventez pas la roue.
Si l'un des concepts dont vous avez besoin existe déjà et peut être utilisé, ne vous fatiguez pas: utilisez le et ne réinventez pas surtout la roue.
Par "arrêt élégant", nous voulons surtout éviter le fameux \texttt{kill\ -9\ \textless{}pid\textgreater{}} (ou équivalent), ou tout autre arrêt brutal d'un processus qui nécessiterait une intervention urgente du
superviseur.
Par "arrêt élégant", nous voulons surtout éviter le fameux \texttt{kill -9 <pid>} (ou équivalent), ou tout autre arrêt brutal d'un processus qui nécessiterait une intervention urgente du superviseur.
En prévoyant une manière élégante d'envoyer un signal de terminaison:
\begin{enumerate}
\item Les requêtes en cours peuvent se terminer au mieux,
\item Le démarrage rapide de nouveaux processus améliorera la balance d'un processus en cours d'extinction vers des processus tout frais, en autorisant l'exécution parallèle d'anciens et de nouveaux "types" de processus
\item Les requêtes en cours peuvent se terminer sans impact majeur,
\item Le démarrage (rapide) de nouveaux processus améliore la balance de charge d'un processus en cours d'extinction vers des processus tout frais, en autorisant l'exécution parallèle d'anciens et de nouveaux "types" de processus
\end{enumerate}
L'intégration de ces mécanismes dès les premières étapes de développement limitera les perturbations et facilitera la prise en compte d'arrêts inopinés (problème matériel, redémarrage du système hôte, etc.).
\caption{Evolution des types de processus, en fonction des arrêts et démarrages}
\end{graphic}
L'intégration de ces mécanismes dès les premières étapes de développement limitera les perturbations et facilitera la prise en compte d'arrêts inopinés (problème matériel, redémarrage du système hôte, etc.).
\section{Similarité des environnements}
\begin{dangerbox}
@ -236,14 +292,19 @@ Faire en sorte que tous les environnements soient les plus similaires possibles
Conserver des environements similaires limite ce genre de désagréments.
\end{memorizebox}
Ceci permet également de proposer à nos utilisateurs un bac à sable dans lequel ils pourront explorer et réaliser des expérimentations en toute sécurité, sans quel cela n'ait d'impact sur un \textit{réel} environnement de production, où les conséquences pourraient être beaucoup plus graves. \cite[p. 9]{data_design}
Ceci permet également de proposer à nos utilisateurs un bac à sable dans lequel ils pourront explorer et réaliser des expérimentations en toute sécurité, sans quel cela n'ait d'impact sur un \textit{réel} environnement de production, où les conséquences pourraient être beaucoup plus graves. \cite[p. 9]{data_intensive}
\section{Journaux de flux évènementiels}
\section{Flux d'évènements}
Les journaux d'exécution sont la seule manière pour une application de communiquer son état et de décrire ce qu'elle est occupée à faire ou à réaliser.
Que nous soyons en phase de développement sur le poste d'une personne de l'équipe, avec une sortie console ou sur une machine de production avec un envoi vers une instance \href{https://www.graylog.org/}{Greylog} ou \href{https://sentry.io/welcome/}{Sentry}, le routage des évènements doit être réalisé en fonction de la nécessité du contexte d'exécution et de sa criticité.
\begin{memorizebox}
Une application ne doit jamais se soucier de l'endroit où les évènements qui la concerne seront écrits, mais se doit simplement de les envoyer sur la sortie \texttt{stdout}.
De cette manière, que nous soyons en développement sur le poste d'un développeur avec une sortie console ou sur une machine de production avec un envoi vers une instance \href{https://www.graylog.org/}{Greylog} ou \href{https://sentry.io/welcome/}{Sentry}, le routage des journaux sera réalisé en fonction de sa nécessité et de sa criticité, et non pas parce que le développeur l'a spécifié en dur dans son code.
\end{memorizebox}
Cette phase est critique, dans la mesure où les journaux d'exécution sont la seule manière pour une application de communiquer son état vers l'extérieur: recevoir une erreur interne de serveur est une chose; pouvoir obtenir un minimum d'informations, voire un contexte de plantage complet en est une autre.
Cette phase est essentielle, dans la mesure où recevoir une erreur interne de serveur est une chose; pouvoir obtenir un minimum d'informations, voire un contexte de plantage complet en est une autre.
La différence entre ces deux points vous fera, au mieux, gagner plusieurs heures sur l'identification ou la résolution d'un problème.
\section{Isolation des tâches administratives}
@ -262,7 +323,7 @@ Au fur et à mesure que le code est délibérément construit pour être mainten
\item
Conservation précise des dépendances nécessaires
\item
Résilience des services et plantage élégant (i.e. \textbf{sans finir un SEGFAULT avec l'OS dans les choux et un écran bleu})
Résilience des services et plantage élégant (i.e. sans finir un SEGFAULT avec l'OS dans les choux et un écran bleu)
\item
Compatibilité entre les différentes versions (n+1, \ldots)
\item
@ -272,5 +333,5 @@ Au fur et à mesure que le code est délibérément construit pour être mainten
\item
Traces des requêtes provenant des utilisateurs, indépendamment des services utilisés
\item
Centralisation de la configuration (\textbf{via} ZooKeeper, par exemple)
Centralisation de la configuration (\textit{via} ZooKeeper, par exemple)
>>> for function_name in [x for x in dir(module_chelou) if "on_command_" in x]:
... getattr(module_chelou, function_name)()
...
Oune
Dos
Tresse
```
\end{minted}
@ -301,17 +301,42 @@ Toute fonction dans la complexité est supérieure à cette valeur sera considé
\section{Conventions de documentation}
Python étant un langage interprété fortement typé, il est plus que conseillé, au même titre que les tests unitaires que nous verrons plus bas, de documenter son code.
Ceci impose une certaine rigueur, tout en améliorant énormément la qualité, la compréhension et la reprise du code par une tierce personne.
Ceci implique aussi de \textbf{tout} documenter: les modules, les paquets, les classes, les fonctions, méthodes, \ldots ce qui peut aller à contrecourant d'autres pratiques \cite{clean_code}{53-74} ; il y a donc une juste mesure à prendre entre "tout documenter" et "tout bien documenter":
Il y a une énorme différence entre la documentation du code et les commentaires associés au code:
\begin{enumerate}
\item\textbf{Les commentaires} décrivent comment le code fonctionne
\item\textbf{La documentation du code} décrit comment utiliser le code.
\end{enumerate}
Pour que le code soit utilisé correctement, il est dès lors important de documenter :
\begin{enumerate}
\item
Ce que représente une classe ou une API
\item
Les attentes d'une interface : codes d'erreurs, statuts possibles, etc.
\end{enumerate}
Les commentaires sont cependant utiles lorsque le code n'est pas évident à lire ou lorsque certaines optimisations auraient été appliquées dans un soucis de performances.
Dans la majorité des cas cependant, si des commentaires doivent être rédigés pour que le code devienne lisible, c'est que ce code n'est pas correctement écrit. \cite{clean_code}{53-74}\footnote{\url{https://www.youtube.com/watch?v=Bf7vDBBOBUA}}
Nous pouvons constater un risque de décorrélation entre le code et ses commentaires :
\begin{listing}[!ht]
\begin{minted}[tabsize=4]{python}
# Check that the maximum length of the array if less than 8
if len(array) < 10:
...
\end{minted}
\end{listing}
Il y a donc une juste mesure à prendre entre "tout documenter" (les modules, les paquets, les classes, les fonctions, méthodes, \ldots) et "bien documenter":
\begin{itemize}
\item
Inutile d'ajouter des watermarks, auteurs, \ldots
Git ou tout VCS s'en sortira très bien et sera beaucoup plus efficace que n'importe quelle chaîne de caractères que vous pourriez indiquer et qui sera fausse dans six mois,
\item
Inutile de décrire quelque chose qui est évident; documenter la méthode \mintinline{python}{get_age()} d'une personne n'aura pas beaucoup d'intérêt
Inutile de décrire quelque chose qui est évident; documenter la méthode \mintinline{python}{get_age()} d'une personne n'aura pas beaucoup d'intérêt,
\item
S'il est nécessaire de décrire un comportement au sein-même d'une fonction, avec un commentaire \emph{inline}, c'est que ce comportement pourrait être extrait dans une nouvelle fonction (qui, elle, pourra être documentée proprement).
\end{itemize}
@ -352,7 +377,141 @@ A priori, vous pourriez tout à fait utiliser le vôtre, sous réserve que les c
\subsection{Numpy}
A remplir
\href{https://numpydoc.readthedocs.io/en/latest/format.html}{Numpy} sépare chaque section avec un pseudo-formatage de titre.
Les sections sont définies selon les types suivants:
\begin{itemize}
\item
Une courte description (de la classe, du module, de la fonction, \ldots).
Chaque module devrait avoir une docstring au tout début du fichier.
Cette docstring peut s'étendre sur plusieurs lignes (\textit{Short summary})
\item
Des avertissements liés à la dépréciation: quand la fonctionnalité viendra à disparaitre (dans quelle version), les raisons de sa dépréciation, et les recommandations pour arriver au même résultat. (\textit{Deprecated})
\item
Une description plus précise des fonctionnalités proposées (et pas de la manière dont l'implémentation est réalisée). (\textit{Extended summary})
\item
Les paramètres attendus, leur type et une description (\textit{Parameters})
\item
La ou les valeurs de retour, accompagnées de leur type et d'une description (\textit{Returns})
\item
Les valeurs générées (\textit{yields}) dans le cas de générateurs (\textit{Yields})
\item
Un objet attendu par la méthode \texttt{send()} (toujours dans le cas de générateurs) (\textit{Receives})
\item
D'autres paramètres, notamment dans le cas des paramètres \texttt{*args} et \texttt{**kwargs}. (\textit{Other parameters})
\item
Les exceptions levées (\textit{Raises})
\item
Les avertissements destinés à l'utilisateur (\textit{Warnings})
\item
Une section "Pour continuer\ldots" (\textit{See also})
\item Des notes complémentaires (\textit{Notes})
\item Des références (\textit{References})
\item Des exemples (\textit{Examples})
\end{itemize}
\begin{listing}[!ht]
\begin{minted}[tabsize=4]{python}
"""Docstring for the example.py module.
Modules names should have short, all-lowercase names. The module name may
(anciennement, \texttt{pep8} justement\ldots\hspace{0pt}), qui analyse
votre code à la recherche d'erreurs de convention.
Le premier niveau est assuré par \href{https://pypi.org/project/pycodestyle/}{pycodestyle}, qui analyse votre code à la recherche d'erreurs de convention de style.
\item
Le deuxième niveau concerne
\href{https://pypi.org/project/pyflakes/}{pyflakes}. Pyflakes est un
\emph{simple}\footnote{Ce n'est pas moi qui le dit, c'est la doc du
projet} programme qui recherchera des erreurs parmi vos fichiers
Python.
Le deuxième niveau est \href{https://pypi.org/project/pyflakes/}{pyflakes}.
Il s'agit d'un \emph{simple}\footnote{Ce n'est pas moi qui le dit, c'est la doc du
projet} programme qui recherchera différents types d'erreurs parmi votre code source.
\item
Le troisième niveau est
\href{https://pypi.org/project/flake8/}{Flake8}, qui regroupe les deux
@ -469,18 +927,12 @@ Il existe plusieurs niveaux de \emph{linters}:
cercles à l'Enfer} est \href{https://pylint.org/}{PyLint}.
\end{enumerate}
PyLint est le meilleur ami de votre \emph{moi} futur, un peu comme quand
vous prenez le temps de faire la vaisselle pour ne pas avoir à la faire
le lendemain: il rendra votre code soyeux et brillant, en posant des
affirmations spécifiques. A vous de les traiter en corrigeant le code ou
en apposant un \emph{tag} indiquant que vous avez pris connaissance de
la remarque, que vous en avez tenu compte, et que vous choisissez malgré
tout de faire autrement.
PyLint est le meilleur ami de votre \emph{moi} futur, un peu comme quand vous prenez le temps de faire la vaisselle pour ne pas avoir à la faire le lendemain: il rendra votre code soyeux et brillant, en posant des affirmations spécifiques.
A vous de les traiter en corrigeant le code ou en apposant un \emph{tag} indiquant que vous avez pris connaissance de la remarque, que vous en avez tenu compte, et que vous choisissez malgré tout de faire autrement.
\begin{listing}[H]
\includegraphics{images/calvin/time-machine.jpg}
\begin{graphic}{images/calvin/time-machine.jpg}
\caption{Calvin \& Hobbes se rendent dans le futur pour récupérer les devoirs que son moi-du-futur aura fait (et pour ne pas avoir à les faire lui-même).}
\end{listing}
\end{graphic}
Pour vous donner une idée, voici ce que cela pourrait donner avec un
code pas très propre et qui ne sert à rien:
@ -506,6 +958,8 @@ code pas très propre et qui ne sert à rien:
\caption{Un morceau de code qui ne sert à rien}
\end{listing}
\subsection{Flake8}
Avec Flake8, nous obtiendrons ceci:
\begin{listing}[!ht]
@ -537,7 +991,10 @@ Nous trouvons des erreurs:
votre application se prendra méchamment les pieds dans le tapis).
\end{itemize}
L'étape d'après consiste à invoquer pylint. Lui, il est directement moins conciliant:
\subsection{Pylint}
L'étape d'après consiste à invoquer pylint.
Lui, il est directement moins conciliant:
\begin{verbatim}
$ pylint test.py
@ -601,16 +1058,97 @@ Cet élément peut être:
\begin{enumerate}
\item\textbf{Une ligne de code}
\item\textbf{Un bloc de code}- une fonction, une méthode, une classe, un module, \ldots
\item\textbf{Un projet entier}, en spécifiant la non-prise en compte au niveau du fichier \texttt{.pylintrc}, qui contient
\item\textbf{Un bloc de code}: une fonction, une méthode, une classe, un module, \ldots
\item\textbf{Un projet entier}
\end{enumerate}
\subsection{Ignorer une ligne de code}
\subsubsection{Ignorer une ligne de code}
\subsubsection{Ignorer un bloc de code}
\subsection{Ignorer un bloc de code}
\subsubsection{Ignorer une catégorie globalement}
\subsection{Ignorer une catégorie globalement}
\subsection{Ruff}
Lui, c'est le petit chouchou à la mode.
\href{https://beta.ruff.rs/docs/}{ruff}
\begin{itemize}
\item
10-100x faster than existing linters
\item
Installable via pip
\item
Python 3.11 compatibility
\item
pyproject.toml support
\item
Built-in caching, to avoid re-analyzing unchanged files
\caption{"La Grande Vague" \url{https://www.peppercarrot.com/sr/viewer/framasoft-src__2020-11-06_la-grande-vague_by-David-Revoy.html} par David Revoy et Framasoft, CC-By}
\end{figure}
@ -19,23 +19,20 @@
--- Robert C. Martin Clean Architecture
\end{quote}
Il y a une raison très simple à aborder le déploiement dès maintenant : à trop attendre et à peaufiner son développement en local, on en oublie que sa finalité sera de se retrouver exposé et accessible depuis un
serveur.
Il est du coup probable d'oublier une partie des désidérata, de zapper une fonctionnalité essentielle ou simplement de passer énormément de temps à adapter les sources pour qu'elles puissent être mises à
disposition sur un environnement en particulier, une fois que leur développement aura été finalisé, testé et validé.
Un bon déploiement ne doit pas dépendre de dizaines de petits scripts éparpillés sur le disque.
L'objectif est qu'il soit rapide et fiable.
Ceci peut être atteint au travers d'un partitionnement correct, incluant le fait que le composant principal s'assure que chaque sous-composant est correctement démarré intégré et supervisé.
Il y a une raison très simple à aborder le déploiement dès maintenant : à trop attendre et à peaufiner son développement pour soi-même, on en oublie que sa finalité sera de se retrouver exposé et accessible depuis un serveur.
Il est probable d'oublier une partie des désidérata, de zapper une fonctionnalité essentielle ou simplement de passer énormément de temps à adapter les sources pour qu'elles puissent être mises à disposition sur un environnement en particulier, une fois que leur développement aura été finalisé, testé et validé.
Aborder le déploiement dès le début permet également de rédiger dès le début les procédures d'installation, de mises à jour et de sauvegardes.
Un bon déploiement ne doit pas dépendre de dizaines de petits scripts éparpillés sur le disque: l'objectif est que celui-ci soit rapide et fiable.
Cet objectif peut être atteint au travers d'un partitionnement correct, incluant le fait que le composant principal s'assure que chaque sous-composant est correctement démarré intégré et supervisé.
Aborder le déploiement dès le début du développement permet également de rédiger les procédures d'installation, de mises à jour et de sauvegardes adaptées.
A la fin de chaque intervalle de développement, les fonctionnalités auront dû avoir été intégrées, testées, fonctionnelles et un code propre, démontrable dans un environnement similaire à un environnement de production, et créées à partir d'un tronc commun au développement
\cite{devops_handbook}.
Déploier une nouvelle version doit être le plus simple possible, et doit se résumer, dans le pire des cas, à quelques lignes d'un script Bash.
\begin{quote}
Because value is created only when our services are running into production, we must ensure that we are not only delivering fast flow, but that our deployments can also be performed without causing chaos and
disruptions such as service outages, service impairments, or security or compliance failures.
Because value is created only when our services are running into production, we must ensure that we are not only delivering fast flow, but that our deployments can also be performed without causing chaos and disruptions such as service outages, service impairments, or security or compliance failures.
--- DevOps Handbook Introduction
\end{quote}
@ -50,11 +47,23 @@ En production:
En restant avec les paramètres par défaut, il est plus que probable que vous rencontriez rapidement des erreurs de verrou parce qu'un autre processus a déjà pris la main pour écrire ses données. En bref, vous avez quelque chose qui fonctionne, qui répond à un besoin, mais qui va attirer la grogne de ses utilisateurs pour des problèmes de latences, pour des erreurs de verrou ou simplement parce que le serveur répondra trop lentement.
\end{itemize}
L'objectif de cette partie est de parcourir les différentes possibilités qui s'offrent à nous en termes de déploiement, tout en faisant en sorte que le code soit le moins couplé possible à sa destination de
production. L'objectif est donc de faire en sorte qu'une même application puisse être hébergées par plusieurs hôtes sans avoir à subir de modifications. Nous vous renvoyons vers les 12-facteurs dont nous
avons déjà parlé et qui vous énormément nous aider, puisque ce sont des variables d'environnement qui vont réellement piloter le câblage entre l'application, ses composants et son hébergement.
L'objectif de cette partie est de parcourir les différentes possibilités qui s'offrent à nous en termes de déploiement, tout en faisant en sorte que le code soit le moins couplé possible à sa destination de production.
L'objectif est donc de faire en sorte qu'une même application puisse être hébergées par plusieurs hôtes sans avoir à subir de modifications. Nous vous renvoyons vers les 12-facteurs dont nous avons déjà parlé et qui vous énormément nous aider, puisque ce sont des variables d'environnement qui vont réellement piloter le câblage entre l'application, ses composants et son hébergement.
RedHat proposait récemment un article intitulé \emph{*What Is IaaS*}\footnote{\url{https://www.redhat.com/fr/topics/cloud-computing/what-is-iaas}}, qui présentait les principales différences entre types d'hébergement.
RedHat proposait récemment un article intitulé \emph{*What Is IaaS*}, qui présentait les principales différences entre types d'hébergement.
\begin{tabular}{r c c c c}
& On-site & IaaS & PaaS & SaaS \\
Applications &&&&\\
Données &&&&\\
Exécution &&&&\\
Middleware &&&&\\
Système d'exploitation &&&&\\
Virtualisation &&&&\\
Serveurs &&&&\\
Stockage &&&&\\
Réseau &&&&\\
\end{tabular}
\begin{figure}[H]
\centering
@ -82,7 +91,9 @@ Dans cette partie, nous aborderons les points suivants:
\item
Définir l'infrastructure et les composants nécessaires à notre application
\item
Configurer l'hôte qui hébergera l'application et y déployer notre application: dans une machine physique, virtuelle ou dans un container. Nous aborderons aussi les déploiements via Ansible et Salt. A ce stade, nous aurons déjà une application disponible.
Configurer l'hôte qui hébergera l'application et y déployer notre application: dans une machine physique, virtuelle ou dans un container.
Nous aborderons aussi les déploiements via Ansible et Salt.
A ce stade, nous aurons déjà une application disponible.
\item
Configurer les outils nécessaires à la bonne exécution de ce code et de ses fonctionnalités: les différentes méthodes de supervision de l'application, comment analyser les fichiers de logs, comment intercepter correctement une erreur si elle se présente et comment remonter correctement l'information.