uv, un package manager Python adapté à la Data - Partie 2 : travaux pratiques

Nous avons vu dans une première partie ici comment uv s’inscrit dans l’écosystème Python, ainsi que ses principales fonctionnalités. Maintenant vade retro théorie ennuyeuse, sortez vos meilleurs Shell et VSCode, et suivez-moi pas à pas dans la mise en place d’un projet de A à Z pour mieux comprendre les différentes options proposées par uv.

Travaux pratiques

Dans le cadre de cette démonstration, je vous propose la mise en place d’un projet très simple, utilisant AWS, qui se contente de charger des données sur une landing zone S3 par appel d’une API REST quelconque à travers le service Lambda. On utilisera ensuite Glue pour monter cette donnée sur un Data warehouse Redshift. Sur celui-ci, toutes les transformations ELT seront organisées par dbt.

Schéma décrivant un processus d'ingestion de données jouet pour notre projet: ingestion lambda sur S3, chargement via Glue sur Redshift puis transformations ELT via dbt
Schéma descriptif de notre projet imaginaire

Parce que c’est super important pour mon histoire, il faut un nom pour ce magnifique projet. Je vous propose DécoUVerte.

💡Attention aux versions
Au moment où j’écris ces lignes, je travaille avec:
uv: 0.6.14
Python pour Lambda: 3.13
Python pour Glue 5.0: 3.11
dbt: 1.9.4
Il se peut que, malgré tout, l’exécution des mêmes commandes, avec les mêmes versions de chaque outil aient des rendus différents sur vos machines. Ça peut être dû au cache d’uv, mais in fine la différence globale ne devrait pas nuire à la compréhension.

Initialisation du répertoire

Il va falloir commencer par créer le répertoire de travail, le module racine qui va gérer tout notre projet (l’équivalent d’un pom aggregate1 Maven).

uv init --no-pin-python --description "Un projet de découverte UV" deco-uv-erte
Capture d'écran des fichiers générés par le uv init racine
Fichiers générés par uv init à la racine

On reconnaît l’usage de la commande init vue précédemment, avec cependant de nouveaux paramètres :

  • --description permet de rajouter la métadonnée “description” dans la table [project]. C’est facultatif, et c’est tout à fait envisageable de gérer ce genre de choses au sein du README, mais je souhaitais vous en montrer la possibilité au moins une fois, pour l’exemple.
  • --no-pin-python a en revanche un comportement plus intéressant. En effet, les modules déclarés par pyproject définissent un intervalle de versions valides de Python. Par exemple dans mon cas, j’ai dans mon pyproject requires-python = ">=3.12". uv interprète cet intervalle et va ensuite choisir une version compatible pour exécuter le code. Pour des raisons de reproductibilité, cette version est ensuite persistée dans un fichier .python-version. Le paramètre --no-pin-python est là pour lui dire “attend, ne choisis pas de version de Python tout de suite, je n’ai pas fini”.

Comme aucun code Python ne sera traité par le module racine, nous n’avons pas besoin de définir de back end. Pour aller plus loin, nous pouvons également supprimer le fichier main.py, et même, dans notre cas, la ligne requires-python = ">=3.12" du pyproject (on choisira une version propre à chacun de nos modules plus tard).

Lambda d’ingestion

On va créer un nouveau module ingestion dans un répertoire lambda au sein du module racine. On appelle ce module nouvellement créé un “workspace”.

uv init lambda/ingestion --python 3.13 --no-readme --description "Lambda d'ingestion pour notre super projet découverte" --build-backend hatch

Pas de magie, uv init lambda/ingestion --python 3.13 crée un module dans un nouveau répertoire lambda/ingestion, avec la version Python 3.13. On connaît déjà --description, --no-readme est une nouveauté. Cette option va nous permettre de gérer un unique README à la racine plutôt que d’en créer un par module. Comme pour la description, cela n’impacte pas le fonctionnement du projet, à votre guise pour l’usage. 

Build back end

Jetons un oeil aux fichiers créés : 

Capture d'écran des fichiers générés par le uv init pour la lambda d'ingestion
Fichiers générés par uv init pour la lambda d'ingestion

[project]
name = "ingestion"
version = "0.1.0"
description = "Lambda d'ingestion pour notre super projet découverte"
authors = [
	{ name = "XXX", email = "XXX@YYY.fr" } # vos informations personnelles apparaîtront ici
]
requires-python = ">=3.13"
dependencies = []

[project.scripts]
ingestion = "ingestion:main"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

  1. --build-backend génère par défaut un src layout, on voit bien l’arborescence src/ingestion.
  2. Ici, on a implicitement créé une --app. C’est ce paramètre en conjonction avec --build-backend qui définit la fonction main de notre application comme point d’entrée (la table [project.scripts] du pyproject.toml)
  3. Par défaut, un package distribuable est généré avec des métadonnées supplémentaires, en l'occurrence l’auteur.

Dans notre cas particulier, un script exécutable localement fait très peu sens (le service Lambda réclame sa propre spécification de point d’entrée). Je vous propose donc de simplement supprimer la table [project.scripts].

Pour ne pas nuire à la lisibilité, on va également supprimer la ligne author.

💡Je vous ai (un peu) menti !
On aurait très bien pu informer uv de ne pas générer ces lignes et s’épargner leur suppression, avec les paramètres `--lib et `--author-from none`. Cependant, notre projet jouet présente le défaut de ne pas contenir de module script pertinent, et je tenais à vous montrer ce qui était généré !

Workspace

Le fichier pyproject à la racine a également été modifié:

Avant Après
[project]
name = "deco-uv-erte"
version = "0.1.0"
description = "Un projet de découverte UV"
readme = "README.md"
dependencies = []
[project]
name = "deco-uv-erte"
version = "0.1.0"
description = "Un projet de découverte UV"
readme = "README.md"
dependencies = []

[tool.uv.workspace]
members = [
	"lambda/ingestion",
]

Le module lambda/ingestion apparaît clairement en tant que membre du projet racine. L’avantage de ce système réside dans un management commun. En effet, tous les workspaces membre du projet seront gérés par la racine, entre autres avec un unique environnement virtuel. Cela nous permet aussi de déclarer des dépendances communes pour tous nos modules. Au hasard, on va vouloir lancer des tests unitaires partout au sein du projet, il serait peut-être utile de factoriser pytest.

uv add pytest --dev

Avant Après
[project]
name = "deco-uv-erte"
version = "0.1.0"
description = "Un projet de découverte UV"
readme = "README.md"
dependencies = []

[tool.uv.workspace]
members = [
	"lambda/ingestion",
]

[project]
name = "deco-uv-erte"
version = "0.1.0"
description = "Un projet de découverte UV"
readme = "README.md"
dependencies = []

[tool.uv.workspace]
members = [
	"lambda/ingestion",
]

[dependency-groups]
dev = [
	"pytest>=8.3.5",
]

De manière symétrique, certaines dépendances vont être spécifiques à certains modules. Dans le cas de notre module d’ingestion qui fait appel à des API REST (d’après notre schéma initial), on imagine facilement son besoin de requests :

uv add requests --package ingestion

--package permet d’interagir avec un workspace membre de notre projet. Maintenant dans le fichier lambda/ingestion/pyproject.toml :

Avant Après
[project]
name = "ingestion"
version = "0.1.0"
description = "..."
requires-python = ">=3.13"


[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "ingestion"
version = "0.1.0"
description = "..."
requires-python = ">=3.13"
dependencies = [
	"requests>=2.32.3",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

Glue Data Loading

On l’a déjà fait une fois, Glue fonctionnera comme pour une Lambda :

uv init glue/data-loading --python 3.11 --no-readme --description "Job de chargement de données" --lib --author-from none

Cette fois, on peut observer la commande complète avec tous les paramètres vu dans l’affaire du mensonge-gate (dans la création de la Lambda). Pour faire bonne figure, on peut y rajouter une dépendance à pyspark :

uv add pyspark --package data-loading

Et là, c’est le drame ! Si on laisse uv choisir la version de pyspark, on se retrouve avec :

...
dependencies = [
	"pyspark>=3.5.5",
]
...

Tandis que la documentation de Glue nous dit que Glue 5.0 fonctionne avec PySpark 3.5.4. (oui je sais, c’est une version de bugfix d’écart, on va considérer que c’est vraiment dramatique sinon toute mon explication tombe à l’eau). 

uv permet, vous vous en doutez, de spécifier une version lors de l’appel à uv add:

uv add pyspark==3.5.4 --package data-loading

💡Glue et les package distributions
Lors du déploiement, pour utiliser notre bibliothèque avec Glue, on spécifiera l’argument --additional-python-module avec le chemin vers le fichier .whl construit par notre backend. Ce fichier est un peu l’équivalent d’un light jar java, seule la spécification des dépendances nécessaires y est présente. C’est ensuite Glue qui se chargera de créer le bon environnement d’exécution au lancement du job.

Pour exploiter le code présent dans notre module, il suffira de faire un import data_loading dans le script exécuté par Glue.

Capture d'écran de la configuration d'AWS Glue ETL pour intégrer un module Python
Paramètres à prendre en compte pour utiliser un module Python dans AWS Glue ETL

dbt

Il ne nous manque plus que la configuration de notre module dbt. On va commencer par l’initialiser. Comme je souhaite conserver un mode d’exécution local pour la démonstration (et parce que c’est plus facile de développer quelque chose qui s’exécute aussi sur son poste), je vous propose de mocker le comportement de Redshift avec DuckDB. Deux arguments pour ça :

  1. Les syntaxes SQL de ces deux entrepôts sont très proches de PostgreSQL, le code sera donc pratiquement interopérable.
  2. J’ai cru lire quelque part que duckdb était un choix plutôt bon pour les POCs et les démos (#autopromo)

uv tool run --from dbt-core --with dbt-duckdb dbt init --profiles-dir . transformations_elt
# ou bien
uvx --from dbt-core --with dbt-duckdb dbt init --profiles-dir . transformations_elt

Capture d'écran des fichiers générés par la commande uvx dbt
Fichiers générés par uvx dbt

Tout beau tout propre, un nouveau projet dbt a été créé au sein de notre répertoire, avec plein de choses dedans. La prochaine étape est de persister les versions de dbt-core et dbt-duckdb utilisées pour la création du projet. On va choisir de manager le projet avec uv, cependant comme il ne s’agit pas d’un répertoire de “code Python”, mais d’un répertoire déjà managé par un outil Python, on va faire le minimum syndical. C’est là que --bare rentre en jeu :

uv init transformations_elt --bare
uv add dbt-core dbt-duckdb --package transformations_elt

Et c’est déjà terminé pour la configuration de dbt ! Tout ça sans jamais avoir installé dbt directement sur son poste. Dernière subtilité pour exécuter les bonnes commandes dbt :

# depuis la racine du projet
uv run --package transformations_elt dbt <COMMAND> --project-dir transformations_elt --profiles-dir transformations_elt

# depuis le répertoire transformations_elt
uv run dbt <COMMAND>

Je vous encourage à tester le bon fonctionnement de la commande uv run dbt debug.

L’écosystème

Et voilà c’est déjà fini ! Ou bien…On a vu une partie de la puissance de dbt dans la mise en place d’un répertoire de développement, cependant tout ça ne servirait à rien sans une intégration en bonne et due forme avec les outils nécessaires et existants : CI/CD, IDE, etc (Conda, c’est toi qu’on regarde). 

L’image Docker

Je ne ferai pas de démonstration pratique pour ce cas là. Je vais me contenter d’ouvrir le champ des possibles :

Astral met à disposition une série d’images Docker basée sur les standard alpine, bookworm, python et autres, qui portent en plus directement une version d’uv déjà installée2. On se remémore dans l'article précédent (rappeler lien)

Il s’agit du seul outil qu’il sera nécessaire d’installer en dehors d’un environnement virtuel

On se souvient également que l’outil est très performant quand il s’agit de construire un environnement virtuel. Ce qui jusque-là était une caractéristique de confort de développement prend tout son sens au moment de la création d’images virtuelles. Que ce soit pour vos déploiements à coup de gitlab-ci ou pour vos conteneurs sur le Cloud, l’image par défaut suffit généralement. 

Plus besoin de préconstruire vos images à coup de Dockerfile parce que pip regarde les marguerites.

Bon allez, je vous montre quand même ce qu’il est possible de faire dans un CI gitlab, pour vous donner une idée du gain en terme d’ergonomie, en retenant tout de même que celui-ci est purement théorique : 

variables:
  CI_JOB_IMAGE_TAG: python3.13-bookworm
  CI_JOB_IMAGE: ghcr.io/astral-sh/uv:${CI_JOB_IMAGE_TAG}

stages:
  - test
  - build

.ci_common:
  image:
    name: $CI_JOB_IMAGE
    entrypoint: [""]
  before_script:
    - uv sync --all-packages

dbt:test_connection:
  extends: .ci_common
  stage: test
  script:
    - uv run pytest
    - uv run dbt compile --profiles-dir transformations_elt --project-dir transformations_elt

dbt:build:
  extends: .ci_common
  stage: build
  script:
    - uv build --all

Le back end

On en a déjà parlé, sans vraiment l’aborder. Il s’agit de l’outil qui va se charger de construire vos distributions Python, soit les archives contenant votre code et sa description. Mais si je vous disais qu’on peut faire bien plus avec un back end ? Selon la technologie utilisée, diverses options de configuration seront disponibles. Ce n’est pas le sujet de cet article de les détailler toutes, alors on va se concentrer sur un exemple : la constitution d’une archive zip contenant le code.

Par défaut, le back end construit deux artefacts différents : une wheel (binary distribution), ainsi qu’une archive .tar.gz (source distribution). A date, le service AWS Lambda ne supporte pas les archives .tar.gz pour le déploiement. Je veux construire un artefact .zip.

Hatchling, le back end qu’on utilise depuis le début de cette démonstration, permet d’appeler des scripts de build arbitraires avec un peu de configuration.

[build-system]
requires = ["hatch-build-scripts"]
build-backend = "hatchling.build"

[[tool.hatch.build.hooks.build-scripts.scripts]]
out_dir = "../../dist"
clean_artifacts = true
commands = [
	"zip -r ingestion.zip src/*",
]
artifacts = [
	"ingestion.zip"
]

Ici, on utilise la dépendance hatch-build-script pour appeler la commande zip et construire l’artefact ingestion.zip. Cet artefact est ensuite copié dans le répertoire dist (le répertoire de build par défaut). On peut ensuite informer son DevOps préféré de son existence et déployer sa Lambda en conséquence.

L’IDE

Parce que, quoi qu’on dise, personne ne développe juste avec sa console et Vim. Je vous propose qu’on configure pas à pas un profil VSCode complet adapté aux technologies utilisées dans la démo. 

😭Mais moi j’utilise Intellij !
Pas de panique Romuald, moi aussi je suis un fervent partisan de la suite JetBrains. Cependant aujourd’hui, je n’ai pas trouvé d’équivalent Idea au plugin VSCode dbt Power User3. Celui-ci fournit une série de fonctionnalités fort agréable dans le cadre d’un projet dbt.

Alors pour faciliter la transition, je t’offre ce doudou-plugin Intellij Idea Keybindings pour retrouver tes raccourcis claviers préférés.

Python Setup

Ce sont les fondations qui vont donner sa cohérence à tout le projet. Tout d’abord, l’installation du plugin Python.

Capture d'écran de l'extension Python VSCode à installer
Extension Python à installer sur VSCode

Une fois l’extension installée, il va falloir lui renseigner le chemin vers l’interpréteur Python à utiliser. Souvenez-vous, uv gère tout seul ses versions de Python, on va donc juste lui expliquer qu’il doit prendre en compte l’exécutable trouvé dans l’environnement virtuel. On s’assure donc avant de son existence:de build arbitraires avec un peu de configuration.

uv sync --all-packages

Ensuite, dans la barre de recherche en haut de la fenêtre, on tape > Select Interpreter, et on lui indique le chemin qui pointe vers le venv local :

Capture d'écran du Setup de l'interpréteur Python sur VSCode
Setup de l'interpréteur Python

dbt Power User

Avant d’installer l’extension, dont l’invité de configuration est quasiment automatique et plutôt bien ficelé, il y a un point de détail technique assez obscur à gérer. dbt Power User utilise nécessairement pip (pas uv pip) pour vérifier l’existence des bibliothèques dbt (et les installer le cas échéant mais cela ne nous intéresse pas, comme on les gère directement via uv). On ajoute donc pip à notre environnement virtuel:

# soit on considère que pip est une bibliothèque générale, et on fait l'install pour tous
uv add pip --dev

# soit on considère que l'install pip est un bugfix pour dbt
uv add pip --dev --package transformations_elt

# le résultat est le même

On peut maintenant configurer dbt Power User, et on pense bien à demander de l’aide pour la configuration:

Capture d'écran de l'extension dbt VSCode à installer
Extension dbt à installer sur VSCode

Capture d'écran du pop-up d'aide dbt Power User
dbt Power User vous demande si vous désirez de l'aide dans un pop-up

Ensuite on suit les étapes, globalement suivant-suivant-suivant-terminé. Je ne vous détaillerai pas l’usage de l’outil, on a déjà toute une compotée d’articles qui traitent le sujet dbt sur le blog Ippon.

Les Linter et Formatter

De manière formelle, les linters sont des outils qui apportent la coloration syntaxique et la mise en valeur des erreurs de syntaxe (les soulignements rouge en zigzag). Les formatters apportent une structure au code en appliquant des règles, comme “mettre un espace après une parenthèse” ou “les lignes ne doivent pas faire plus de 120 caractères”. Ils sont spécifiques à un langage. Il va donc falloir des outils différents pour le Python et le SQL. Parfois leurs domaines se chevauchent, cependant nous allons essayer de séparer nettement les responsabilités :

Linter Python : En installant l’extension Python, on a également installé l’extension Pylance comprise avec. C’est le linter proposé par défaut, il conviendra très bien.

Formatter Python : Astral, l’entreprise derrière uv, a également créé Ruff, le formatter Python. Il serait dommage de s’en passer dans cette démonstration dédiée à uv :

Capture d'écran de l'extension Ruff VSCode à installer
Extension Ruff (Formatter Python) à installer sur VSCode

Cette extension embarque sa propre version de Ruff, cependant si vous souhaitez gérer la vôtre, il utilisera en priorité celle présente au sein de l’environnement virtuel. Il vous suffira donc de l’installer : 

uv add ruff --dev

Vous pouvez vérifier que la bonne version est prise en compte en allant dans la barre de recherche utiliser la commande:

Capture d'écran de la procédure d'affichage des paramètres de Ruff
Affichage des paramètres utilisés par Ruff

Le chemin de l’exécutable doit pointer vers l’environnement virtuel.

Formatter SQL : dbt Power User utilise par défaut sqlfmt comme formatter, mais lui a nécessairement besoin d’un exécutable. De même:

uv add shandy-sqlfmt[jinjafmt] --package transformations_elt --dev

💡le nom de la bibliothèque est capilotractée
on peut le retrouver dans la doc de configuration dbt4

Linter SQL : en terme de linter SQL avec dbt, on va bêtement suivre les recommandations de sqlfmt :

You can (and should!) use SQLFluff to lint your SQL queries after they are formatted by sqlfmt5

Il s’agit à la base d’un linter SQL généraliste, compatible avec de nombreux dialectes SQL. Sa valeur ajoutée ? Fonctionner avec plusieurs modes, appelés templater différents, dont un compatible purement jinja, et un exploitant le code compilé par dbt pour analyser les fichiers produits. Le premier sera plus rapide, le second plus exhaustif, l’objectif ici n’étant pas de démontrer toutes les fonctionnalités de SQLFluff, je vous laisserai comparer. Pour notre exemple, nous allons utiliser une configuration générique avec le templater dbt :

uv add sqlfluff --package transformations_elt --dev
uv add  sqlfluff-templater-dbt --package transformations_elt --dev

Ensuite, on installe l’extension et on la configure, en renseignant le chemin vers l’exécutable (oui, malheureusement cette fois-ci ce n’est pas automatique), puis en complétant les informations spécifiques pour dbt. Les options à modifier étant disséminées un peu partout dans la configuration, je vous recommande d’éditer directement votre fichier settings.json :

Capture d'écran de l'extension SQLFluff VSCode à installer
Extension SQLFluff (Linter SQL) à installer sur VSCode

"sqlfluff.executablePath": "${workspaceFolder}/.venv/bin/sqlfluff",
"sqlfluff.linter.run": "onSave",
"sqlfluff.experimental.format.executeInTerminal": true,
"editor.formatOnSave": false

Pour fonctionner, SQLFluff se base sur deux fichiers de configuration, .sqlfluff et .sqlfuffignore, respectivement pour la configuration et la spécification des chemins où aller chercher des fichiers.Sans les détailler (je vous redirige vers la documentation de SQLFluff pour définir vos propres règles), voici ceux que j’ai utilisé dans notre exemple :

#.sqlfuffignore
target/
#.sqlfluff
[sqlfluff]
templater = dbt
dialect = duckdb
exclude_rules = layout.indent, layout.cte_bracket, layout.select_targets, layout.spacing, layout.keyword_newline
# set max_line_length to whatever you set in sqlfmt
max_line_length = 88

[sqlfluff:rules]
capitalisation_policy = lower
extended_capitalisation_policy = lower

[sqlfluff:rules:convention.terminator]
multiline_newline = True

[sqlfluff:templater:dbt]
project_dir = ./transformations_elt/
profiles_dir = ./transformations_elt/

Conclusion

That’s all folks ! Une dernière fonctionnalité pour la route. On se rappelle ce qu’on cherchait à faire en utilisant uv pour gérer notre projet Data : 

Obtenir un environnement de travail partageable et reproductible au sein d’une équipe de dev. 

Pour valider tout ça, je vous propose de vérifier que ce qu’on a mis en place est bien partageable et reproductible. Vous pouvez trouver tout le contenant de la mise en place de l’environnement sur Github, à vous de vous mettre à la place d’un développeur qui débarque sur le projet, de cloner ce repository et de valider que tout fonctionne ! En prime, vous trouverez dans ce répertoire git un fichier deco-uv-erte.code-profile vous permettant de recréer chez vous tout l’environnement VSCode, sans configuration supplémentaire. Pour rappel, dans votre onglet profile :

Capture d'écran de l'opération d'import Profile sur VSCode
Import d'un Profil sur VSCode

On n’oublie pas non plus de synchroniser son environnement virtuel quand on clone un projet :

uv sync --all-packages

A vos claviers, et maintenant, à vous de démontrer qu’uv est vraiment un package manager sympa ! (je ne vais quand même pas faire mon travail jusqu’au bout non mais).

Pour avoir une vrai conclusion tout de même, je ne vous ai présenté ici qu’une petite partie d’uv, et j’ai beaucoup éludé la configuration de certains outils. Je vous encourage de nouveau à aller consulter les diverses documentations. Je pense plus précisément :

  • aux fonctions de gestion des versions de Python d’uv, que j’ai à peine présenté
  • aux paramètres complémentaires à  --app, --lib, --bare, que j’ai volontairement évité pour des raisons de simplicité (--package, --no-package)
  • à SQLFluff, décrire l’outil en entier demanderai un article à part entière 🙂
  • cette conférence de Françoise Conil, qui parle de packaging Python beaucoup mieux que moi (et qui m’a également permis de découvrir uv)

Ressources

Ressources de l’article précédent

https://astral.sh

https://docs.astral.sh/uv/getting-started/installation

https://docs.astral.sh/uv/concepts/tools

https://github.com/astral-sh/uv

https://github.com/pypa/hatch

https://alpopkes.com/posts/python/packaging_tools

https://pythonwheels.com

https://peps.python.org/pep-0517

https://peps.python.org/pep-0621

https://packaging.python.org/en/latest/discussions/src-layout-vs-flat-layout

https://softwaremill.com/what-to-do-with-your-end-of-life-akka

Ressources

https://blog.ippon.fr/2025/05/12/uv-un-package-manager-python-adapte-a-la-data-partie-1-theorie-et-fonctionnalites

https://docs.astral.sh/uv/guides/integration/docker/#available-images

https://marketplace.visualstudio.com/items?itemName=innoverio.vscode-dbt-power-user

https://marketplace.visualstudio.com/items?itemName=k--kato.intellij-idea-keybindings

https://docs.getdbt.com/reference/profiles.yml

https://docs.sqlfluff.com/en/stable/configuration/index.html

https://docs.sqlfmt.com/integrations/sqlfluff

https://github.com/jeliermann/deco-uv-erte

https://blog.ippon.fr/tag/dbt/

https://blog.ippon.fr/2025/03/10/accelerez-vos-poc-avec-duckdb-et-python/

https://perso.liris.cnrs.fr/francoise.conil/recherche-des-bonnes-pratiques-de-packaging