Alchimie Data Solutions — adsToolBox
adsToolBox est une librairie Python interne d'Alchimie Data Solutions, qui regroupe les
fonctions génériques réutilisées dans les développements liés à Onyx. Elle fournit des
briques homogènes pour accéder aux bases de données, industrialiser des pipelines, capturer
du changement (CDC), manipuler des fichiers sur différents protocoles, et gérer les tâches
transverses (logs, chrono, environnement, mails, Git, Odoo, Google Calendar).
Dépôt privé — ce repository est réservé aux employés d'Alchimie Data Solutions. Le package est toutefois publié publiquement sur PyPI sous le nom
adstoolbox, et un dépôt d'exemples publics est disponible : AlchimieDataSolutions/DemoPy.
- Nom du package :
adstoolbox - Module Python :
adsToolBox - Versioning : calendaire (
YYYY.MM.DD) — voirpyproject.toml - Python :
>= 3.10, < 4.0 - Licence : MIT
Sommaire
- Fonctionnalités
- Installation
- Modules et extras
- Démarrage rapide
- Structure du dépôt
- Tests
- Développement
- Dépendances
- Auteurs
- Licence
Fonctionnalités
Tous les symboles ci-dessous sont exposés directement depuis adsToolBox (voir adsToolBox/__init__.py).
La colonne Extra indique la dépendance optionnelle à installer — voir
Modules et extras.
Bases de données et pipelines
| Symbole | Extra | Rôle |
|---|---|---|
DataFactory |
dataframe |
Classe abstraite commune : connect, sql_query, sql_exec, sql_scalaire, insert, insert_many, insert_bulk, upsert, upsert_many, upsert_bulk, find_text_anywhere… |
DbMssql |
mssql |
Implémentation SQL Server (driver pymssql). |
DbMysql |
mysql |
Implémentation MySQL (driver pymysql). |
DbPgsql |
pgsql |
Implémentation PostgreSQL (driver psycopg2). |
Pipeline |
dataframe |
Orchestration d'un transfert source → destination avec batch, déduplication par hash, inférence de schéma Polars, création automatique de la table cible. |
DataComparator |
dataframe |
Compare deux sources batch par batch et produit un rapport de différences. |
ChangeDataCapture |
cdc |
CDC déclarative (modes append, scd1, scd2, scd4) validée par JSON Schema, avec gestion staging/persistent et synchronisation côté métier. Sur PostgreSQL, requiert l'extension pgcrypto. |
Fichiers, infrastructure, intégrations externes
| Symbole | Extra | Rôle |
|---|---|---|
FileHandler |
files |
Accès fichiers multi-backends via fsspec (local, SMB, SFTP, Azure Blob) avec transfert atomique, checksum optionnel et transformation du flux à la volée. |
GitHandler |
git |
Clonage / mise à jour de dépôts Git via token (GitPython + API GitHub). Exige aussi le binaire git dans le PATH. |
GoogleCalendarConnector |
google |
Lecture/écriture d'événements Google Calendar (OAuth2). |
MailReader |
— | Lecture IMAP avec décodage robuste des en-têtes et du corps (multipart). |
OdooConnector |
— | Accès XML-RPC à Odoo (get, put, …). |
Utilitaires transverses
Aucun extra requis : ces symboles fonctionnent avec l'installation de base.
| Symbole | Rôle |
|---|---|
Logger |
Logger unifié console / fichier / base. Configuration par console_config, file_config et tables_config. Fichier nommé d'après la chaîne d'appel, rotation calendaire et purge par ancienneté. Contexte disabled(). |
timer, get_timer, set_timer, now, set_timezone |
Décorateur de chronométrage et helpers de temps (timezone-aware). |
retry_on_failure |
Décorateur de retry avec backoff et méthode de reconnexion optionnelle. |
get_public_ip |
Récupération de l'IP publique (utile pour pare-feux). |
Env |
Chargement d'un .env trouvé automatiquement dans l'arborescence parente. Accès par attribut, indexation ou get(), et bind() pour une configuration typée et validée depuis une dataclass. |
Installation
Utilisation du package (public)
Le package est publié sur PyPI et installable par n'importe qui :
pip install adstoolbox
Cette installation de base est volontairement légère (environ 20 Mo) et couvre Logger,
Env, timer, MailReader et OdooConnector. Les autres modules demandent un extra,
à choisir selon les besoins :
pip install "adstoolbox[pgsql]" # PostgreSQL + Polars
pip install "adstoolbox[mssql,mysql,pgsql]" # les trois bases
pip install "adstoolbox[files]" # FileHandler (fsspec + backends)
pip install "adstoolbox[all]" # tout (environ 480 Mo)
Les guillemets sont nécessaires sous zsh, qui interprète les crochets.
Migration depuis les versions ≤ 2026.05.19 Ces versions installaient toutes les dépendances d'office. Depuis, elles sont optionnelles. Pour retrouver le comportement précédent en une commande :
pip install "adstoolbox[all]". Les imports étant paresseux, une dépendance manquante ne casse plusimport adsToolBox: l'erreur survient à l'utilisation du module concerné, et nomme l'extra à installer.
Développement (interne ADS uniquement)
L'accès aux sources est restreint aux employés d'Alchimie Data Solutions. Une fois le
dépôt cloné via les accès internes, le projet est géré avec Poetry
(voir pyproject.toml et poetry.lock) :
poetry install --all-extras
--all-extras est indispensable : sans lui, ni Polars, ni fsspec, ni les drivers SQL ne
sont installés, et la suite de tests échoue dès la collecte. Le groupe dev
(pytest, testcontainers, ruff) est inclus par défaut.
Modules et extras
Les modules sont chargés paresseusement (PEP 562) : import adsToolBox n'importe
aucune dépendance tierce, chaque module n'est chargé qu'au premier accès à l'un de ses
symboles. Une dépendance absente produit un message explicite :
ImportError: DbPgsql requiert une dépendance non installée (No module named 'psycopg2').
Installez-la avec : pip install "adstoolbox[pgsql]".
| Extra | Paquets installés | Modules débloqués |
|---|---|---|
| (aucun) | requests, chardet, python-dotenv, tzdata (Windows) |
logger, timer, global_config, load_env, mail_reader, odoo, dml_generator |
dataframe |
polars |
data_factory, pipeline, data_comparator |
mssql |
polars, pymssql |
db_mssql |
mysql |
polars, pymysql |
db_mysql |
pgsql |
polars, psycopg2-binary |
db_pgsql |
files |
fsspec, adlfs, smbprotocol, paramiko |
file_handler |
git |
GitPython, PyGithub |
git_handler |
google |
polars, google-api-python-client, google-auth, google-auth-oauthlib |
google_calendar |
cdc |
SQLAlchemy, jsonschema |
cdc, ddl_operations |
all |
tous les précédents | tous |
Les extras des bases incluent polars parce que db_* en dépend via data_factory :
adstoolbox[pgsql] est donc autosuffisant.
L'extra cdc n'apporte que SQLAlchemy et le validateur de schéma : le driver de la base
visée vient de l'extra de cette base. Sur PostgreSQL, le CDC exige en outre l'extension
pgcrypto, vérifiée au démarrage, car l'empreinte de clé repose sur digest() :
CREATE EXTENSION pgcrypto;
Note sur Polars et les anciens processeurs
Polars charge son moteur Rust depuis un paquet de runtime séparé, compilé pour un jeu d'instructions récent. Sur un processeur qui ne le gère pas, l'import échoue ou avertit avant de planter. Installez le runtime de compatibilité :
pip install "adstoolbox[pgsql]"
pip install "polars[rtcompat]"
polars[rtcompat] tire polars-runtime-compat, qui suit les versions de Polars. Il
remplace l'ancien conseil basé sur polars-lts-cpu, distribution figée à la version
1.33.1 et qui fournissait un module polars concurrent : les deux s'écrasaient
mutuellement et rendaient import polars inutilisable. Si vous avez appliqué cette
méthode, désinstallez polars-lts-cpu avant de passer à rtcompat.
Démarrage rapide
Connexion à une base de données
Nécessite pip install "adstoolbox[pgsql]".
from adsToolBox import DbPgsql, Logger, Env
logger = Logger(log_level=Logger.INFO, logger_name="adsLogger")
env = Env(logger)
db = DbPgsql({
'database': env.PG_DWH_DB,
'user': env.PG_DWH_USER,
'password': env.PG_DWH_PWD,
'port': env.PG_DWH_PORT,
'host': env.PG_DWH_HOST
}, logger)
db.connect()
generator = db.sql_query("SELECT * FROM table_test;")
for batch in generator:
for row in batch:
data = row
Pipeline source → destination
Nécessite pip install "adstoolbox[dataframe]", plus l'extra de chaque base utilisée.
from adsToolBox import Pipeline
pipeline = Pipeline(
{
"db_source": db_src,
"query_source": "SELECT * FROM source_table",
"db_destination": {
"name": "demo",
"db": db_dst,
"table": "destination_table",
"cols": ["col1", "col2"],
"cols_def": ["INT", "VARCHAR(50)"],
},
"operation_type": "insert",
"insert_method": "bulk",
"batch_size": 10_000,
},
logger,
)
results = pipeline.run()
print(f"{results['nb_lines_success']} insérées, {results['nb_lines_error']} en erreur")
run() ne lève pas sur un lot en échec : il poursuit et consigne les rejets. Testez
nb_lines_error, pas l'absence d'exception. La clé errors contient les données
rejetées et non seulement leur cause : ne la journalisez pas telle quelle, elle peut
peser des milliers de lignes.
cols_def ne contient que des TYPES, jamais de contraintes : "INTEGER PRIMARY KEY"
y serait du DDL valide pour create_destination_table, mais retomberait en Utf8
côté Polars. Déclarez les contraintes dans le CREATE TABLE.
Un type SQL absent de la table de correspondance retombe sur Utf8, avec une trace en
DEBUG. C'est volontaire pour NUMERIC et DECIMAL : un Float64 perd la précision
au-delà d'une quinzaine de chiffres significatifs.
Avec insert_method="bulk", le DataFrame circule tel quel jusqu'au COPY : aucune
conversion en tuples intermédiaires.
Logger et chronomètre
Aucun extra nécessaire.
from adsToolBox import Logger, set_timer, timer
set_timer(state=True)
class MyJob:
def __init__(self) -> None:
self.logger = Logger(log_level=Logger.DEBUG, logger_name="monJob")
@timer
def run(self) -> None:
self.logger.info("traitement en cours")
Sans configuration, le fichier de log est nommé d'après la chaîne d'appel — un
main.py qui appelle un sample.py porteur du logger écrit dans
main-sample-log.txt — puis archivé chaque jour sous la date de ses anciens logs,
et purgé au-delà de dix jours. Chaque réglage se change par dictionnaire :
Logger(
Logger.INFO, "monJob",
console_config={"enabled": True, "timestamp": False, "name": False},
file_config={"filename": "jobs/etl.log", "retention_days": 30},
tables_config={"logs": "MES_LOGS"},
)
console_config ne règle que la console : le fichier conserve toujours son
horodatage. Une clé inconnue est refusée avec la liste de celles admises.
Configuration typée depuis le .env
Aucun extra nécessaire.
from dataclasses import dataclass
from adsToolBox import Env, Logger
@dataclass
class PgConfig:
db: str
user: str
port: int = 5432
ssl: bool = False
env = Env(Logger())
env.PG_DWH_DB # str, comme avant
pg = env.bind(PgConfig, prefix="PG_DWH_") # PgConfig(db=..., port=5433, ssl=True)
bind convertit selon les annotations (int, float, bool, Path,
int | None), applique les valeurs par défaut et signale toutes les variables
manquantes ou invalides en une fois, au démarrage. Les modèles pydantic sont
acceptés si la bibliothèque est installée ; les dataclasses n'ajoutent aucune
dépendance.
D'autres exemples sont disponibles dans le dépôt de démo : AlchimieDataSolutions/DemoPy.
Structure du dépôt
adsGenericFunctions/
├── .github/workflows/ # CI : tests, matrice d'extras, contrôle du paquet publié
├── adsToolBox/ # Package publié
│ ├── __init__.py # Exports publics et chargement paresseux (PEP 562)
│ ├── cdc.py # ChangeDataCapture + modes SCD
│ ├── data_comparator.py # DataComparator
│ ├── data_factory.py # DataFactory (classe abstraite)
│ ├── db_mssql.py # DbMssql
│ ├── db_mysql.py # DbMysql
│ ├── db_pgsql.py # DbPgsql
│ ├── ddl_operations.py # Génération DDL multi-dialecte
│ ├── dml_generator.py # Génération DML multi-dialecte
│ ├── file_handler.py # FileHandler (fsspec)
│ ├── git_handler.py # GitHandler
│ ├── global_config.py # retry_on_failure, set_timer, get_public_ip
│ ├── google_calendar.py # GoogleCalendarConnector
│ ├── load_env.py # Env
│ ├── logger.py # Logger
│ ├── mail_reader.py # MailReader
│ ├── odoo.py # OdooConnector
│ ├── pipeline.py # Pipeline
│ └── timer.py # timer, now, set_timezone, get_timer
├── scripts/
│ └── check_extras.py # Valide le contrat des extras (utilisé par la CI)
├── tests/ # Tests unitaires (mocks)
├── integration_tests/ # Tests fonctionnels (testcontainers → Docker)
├── adsGenericFunctions.py # Point d'entrée historique
├── pyproject.toml # Métadonnées PEP 621 + configuration Ruff
├── poetry.lock # Versions figées (source de vérité pour la CI)
└── pytest.ini # Configuration pytest
Tests
Les tests sont répartis en deux suites, déclarées dans pytest.ini :
tests/— tests unitaires avec mocks (unittest.mock), sans dépendance externe.integration_tests/— tests fonctionnels avec Testcontainers (SQL Server, MySQL, PostgreSQL, Samba, Azurite) ; Docker doit être disponible.
Les deux suites supposent une installation --all-extras : plusieurs fichiers de test
importent polars et fsspec directement, donc une installation partielle échoue à la
collecte.
Exécuter toutes les suites
poetry run pytest
Cibler une suite
poetry run pytest tests/ # unitaires uniquement
poetry run pytest integration_tests/ # fonctionnels uniquement
poetry run pytest tests/test_logger.py # un fichier précis
Tests de garde sur le chargement paresseux
tests/test_init_lazy.py protège trois propriétés faciles à casser en silence :
import adsToolBoxne charge aucune dépendance tierce ;timerreste la fonction et n'est pas masqué par le sous-module homonyme ;- aucun symbole exporté n'est masqué par un sous-module de même nom.
Ces tests s'ignorent d'eux-mêmes pour les cas dont l'extra est absent, et sont donc exécutables sur une installation partielle.
Contrat des extras
python scripts/check_extras.py # aucun extra installé
python scripts/check_extras.py pgsql # extras installés
python scripts/check_extras.py all
Le script vérifie que le cœur reste importable et que chaque symbole indisponible nomme un extra qui n'est effectivement pas installé. La CI l'exécute sur une matrice de configurations : c'est le seul endroit capable de détecter une régression de couplage, le job principal installant tout.
Tests fonctionnels — prérequis
- Docker Desktop (ou équivalent) lancé et accessible.
- Les containers sont démarrés automatiquement par les fixtures (pas de setup manuel).
- Sous Windows, la variable
TESTCONTAINERS_RYUK_DISABLED=trueest positionnée par les tests pour éviter les problèmes de cleanup. - Les tests CDC créent l'extension
pgcryptodans le conteneur PostgreSQL : l'empreinte de clé passe pardigest(), absent d'une installation nue.
Développement
Linter / formatter
Le projet utilise Ruff (configuration dans pyproject.toml, select = ["ALL"] avec
quelques exceptions documentées) :
poetry run ruff check .
poetry run ruff format .
Cibles configurées : adsToolBox, tests, integration_tests, scripts.
Longueur de ligne : 100.
Conventions
- Python ≥ 3.10, typage explicite et
from __future__ import annotationsquand utile. - Docstrings en français, style concis.
- Noms en
snake_casepour les méthodes publiques. - Tests unitaires obligatoires pour toute nouvelle méthode publique.
Ajouter une dépendance
Toute nouvelle dépendance tierce doit être optionnelle et rattachée à un extra, sauf si elle est pure Python et de taille négligeable. Le critère : un paquet binaire (donc susceptible d'échouer à l'installation), lourd, ou exigeant un binaire système va en extra.
Trois endroits à mettre à jour de façon cohérente :
[project.optional-dependencies]danspyproject.toml, extra dédié et listeall;_EXTRA_OF_MODULEdansadsToolBox/__init__.py, pour le message d'erreur ;- la matrice du workflow CI, si l'extra est nouveau.
Le module concerné doit importer sa dépendance au niveau module (jamais depuis
__init__.py), afin que le chargement paresseux isole la panne.
Publication
Le package est publié sur PyPI sous le nom adstoolbox. La version suit un schéma
calendaire YYYY.MM.DD défini dans pyproject.toml.
poetry build
poetry publish
Dépendances
Les dépendances sont déclarées dans pyproject.toml, sections [project.dependencies]
(cœur) et [project.optional-dependencies] (extras). Les versions sont exprimées en
bornes ouvertes ; poetry.lock fixe les versions exactes pour la CI.
Cœur — toujours installé
Pur Python, taille négligeable, aucun risque d'échec d'installation.
requests,chardet—global_config,mail_readerpython-dotenv—Envtzdata— base IANA pourzoneinfo(Windows uniquement, via marqueur d'environnement)
Extras
- Données :
polars - Bases de données :
pymssql(MSSQL),pymysql(MySQL),psycopg2-binary(PostgreSQL) - CDC :
SQLAlchemy,jsonschema - Fichiers :
fsspec, plus les backends qu'il charge dynamiquement —adlfs(Azureabfs://),paramiko(SFTPsftp://),smbprotocol(SMBsmb://). Ces trois paquets ne sont jamais importés directement mais sont requis à l'exécution. - Intégrations :
GitPython,PyGithub,google-api-python-client,google-auth,google-auth-oauthlib
Développement
Groupe dev du pyproject.toml, jamais installé chez les utilisateurs :
pytest, testcontainers, ruff.
Auteurs
- Olivier Siguré — olivier.sigure@alchimiedatasolutions.com
- Matthieu Vannin — matthieu.vannin@alchimiedatasolutions.com
- Antoine Ducoulombier — antoine.ducoulombier@alchimiedatasolutions.com
- Pierre Baux — pierre.baux@alchimiedatasolutions.com
Licence
Distribué sous licence MIT.
Metadata
Release files for adstoolbox 2026.10.13
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| adstoolbox-2026.10.13.tar.gz | 62.2 kB | Without provenance | Details for adstoolbox-2026.10.13.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| adstoolbox-2026.10.13-py3-none-any.whl | Python 3 | none | any | Details for adstoolbox-2026.10.13-py3-none-any.whl |
Total release size: 137.4 kB
Release files / adstoolbox-2026.10.13.tar.gz
| Download URL | adstoolbox-2026.10.13.tar.gz |
|---|---|
| Size | 62.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
65cb829e35a0b32e2e63d92bf438f2ca291f48490046b622baa04dd65510e5a1
|
|
BLAKE2b-256 checksum How to use checksums |
a1570b7896779d72284f98efc08124cd0a7f273606105c31a570416b674308e8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.4.3 CPython/3.10.11 Windows/10
|
Release files / adstoolbox-2026.10.13-py3-none-any.whl
| Download URL | adstoolbox-2026.10.13-py3-none-any.whl |
|---|---|
| Size | 75.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3608fd1de9fc0ee6471de24449e249b071a35ce5d19ff0aeac3bd4ffeefe4da7
|
|
BLAKE2b-256 checksum How to use checksums |
453a637ceead5ccdde349f65031f09df182d1923d591a2a30acd253ce1eec0d2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.4.3 CPython/3.10.11 Windows/10
|
Security for adstoolbox 2026.10.13
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.