Access to ETNIC web services through Python
Project description
pyetnic
Bibliothèque Python d'accès aux services web SOAP d'ETNIC pour l'enseignement de promotion sociale (Wallonie-Bruxelles).
Fonctionnalités
| Namespace | Service | Opérations |
|---|---|---|
pyetnic.eprom |
Formations | Lister les formations organisables (catalogue), lister les formations organisées |
pyetnic.eprom |
Organisation | Créer, lire, modifier, supprimer une organisation de formation |
pyetnic.eprom |
Document 1 | Lire, modifier, approuver le document de population (inscriptions) |
pyetnic.eprom |
Document 2 | Lire, modifier le document des périodes d'activités d'enseignement |
pyetnic.eprom |
Document 3 | Lire, modifier le document des attributions d'enseignants |
pyetnic.seps |
Recherche étudiants | Lire un étudiant par numéro CF, rechercher par NISS ou nom (prod uniquement) |
pyetnic.seps |
Enregistrement étudiants | Enregistrer un nouvel étudiant, modifier un étudiant existant (prod uniquement) |
pyetnic.seps |
Inscriptions | Rechercher, enregistrer, modifier des inscriptions d'étudiants (prod uniquement) |
Installation
pip install pyetnic
Pour utiliser les services SEPS (signature X509) :
pip install pyetnic[seps]
Configuration
Générer un fichier .env de départ :
pyetnic init-config
Remplir les valeurs dans .env :
# Environnement : dev (services-web.tq.etnic.be) ou prod (services-web.etnic.be)
ENV=dev
# Identifiants pour le développement
DEV_USERNAME=
DEV_PASSWORD=
# Identifiants pour la production
PROD_USERNAME=
PROD_PASSWORD=
# Paramètres par défaut
DEFAULT_ETABID= # identifiant établissement (int)
DEFAULT_IMPLID= # identifiant implantation (int)
DEFAULT_SCHOOLYEAR=2024-2025
# Certificat X509 pour les services SEPS (chemin relatif au répertoire courant)
# Fourni par ETNIC (IAM-PROD). Uniquement nécessaire pour les services SEPS.
SEPS_PFX_PATH=
SEPS_PFX_PASSWORD=
Les identifiants EPROM sont fournis par ETNIC pour accéder aux services web de votre établissement.
Le certificat SEPS (fichier .pfx) est fourni séparément par ETNIC via IAM-PROD.
Configuration programmatique
Pour les applications qui gèrent leurs propres credentials (ex. Django), il est possible de configurer pyetnic sans fichier .env :
from pyetnic.config import Config
Config.ENV = "prod"
Config.USERNAME = "my_user"
Config.PASSWORD = "my_pass"
# Optionnel : charger explicitement un fichier .env
Config.load_from_dotenv()
La configuration est lazy : rien n'est résolu à l'import. Les overrides programmatiques ont priorité sur les variables d'environnement.
Namespaces
pyetnic expose deux namespaces publics :
-
pyetnic.eprom— services EPROM (Enseignement de Promotion Sociale) : formations, organisations, documents 1/2/3. Authentification : WSSE UsernameToken (credentials dev ou prod). -
pyetnic.seps— services SEPS (registre des étudiants CFWB). Authentification : certificat X509 (PFX). Production uniquement. Nécessitepip install pyetnic[seps].
Toutes les fonctions, modèles et exceptions publiques s'importent directement depuis ces namespaces :
from pyetnic.eprom import lire_organisation, OrganisationId, EtnicBusinessError
from pyetnic.seps import lire_etudiant, NissMutationError
L'ancien namespace plat (pyetnic.lire_organisation) n'est plus documenté et ne devrait pas être utilisé dans du nouveau code.
Utilisation
Formations
import pyetnic
# Formations organisables (catalogue de l'année)
result = pyetnic.eprom.lister_formations_organisables(annee_scolaire="2024-2025")
for formation in result:
print(formation.numAdmFormation, formation.codeFormation, formation.libelleFormation)
# Formations déjà organisées (avec leurs organisations et statuts de documents)
result = pyetnic.eprom.lister_formations(annee_scolaire="2024-2025")
for formation in result:
for org in formation.organisations:
print(
f"F{org.id.numAdmFormation}/org{org.id.numOrganisation}",
org.dateDebutOrganisation, "→", org.dateFinOrganisation,
org.statutDocumentOrganisation.statut if org.statutDocumentOrganisation else "—",
)
Organisation
from datetime import date
from pyetnic.eprom import OrganisationId, lire_organisation, creer_organisation
from pyetnic.eprom import modifier_organisation, supprimer_organisation
org_id = OrganisationId(
anneeScolaire="2024-2025",
etabId=3052,
numAdmFormation=455,
numOrganisation=1,
)
# Lire
org = lire_organisation(org_id)
# Créer (numOrganisation attribué par le serveur)
org = creer_organisation(
annee_scolaire="2025-2026",
etab_id=3052,
impl_id=6050,
num_adm_formation=455,
date_debut=date(2025, 9, 15),
date_fin=date(2026, 6, 26),
)
# Modifier
org.dateFinOrganisation = date(2026, 6, 20)
org = modifier_organisation(org)
# Supprimer
ok = supprimer_organisation(org_id)
Document 1 — Population
from pyetnic.eprom import lire_document_1, modifier_document_1, approuver_document_1
from pyetnic.eprom import Doc1PopulationListSave, Doc1PopulationLineSave
doc1 = lire_document_1(org_id)
# doc1 est None si le document n'est pas accessible (statut org insuffisant)
# Modifier
liste_save = Doc1PopulationListSave(population=[
Doc1PopulationLineSave(coAnnEtude=1, nbEleveA=12, nbEleveTotHom=5, nbEleveTotFem=7),
])
doc1 = modifier_document_1(org_id, population_liste=liste_save)
# Approuver
doc1 = approuver_document_1(org_id)
Document 2 — Périodes d'activités
from pyetnic.eprom import lire_document_2, modifier_document_2
from pyetnic.eprom import Doc2ActiviteEnseignementListSave, Doc2ActiviteEnseignementLineSave
doc2 = lire_document_2(org_id)
liste_save = Doc2ActiviteEnseignementListSave(activiteEnseignement=[
Doc2ActiviteEnseignementLineSave(coNumBranche=1, nbEleveC1=15, nbPeriodePrevueAn1=32.0),
])
doc2 = modifier_document_2(org_id, activite_enseignement_liste=liste_save)
Document 3 — Attributions d'enseignants
from pyetnic.eprom import (
lire_document_3, modifier_document_3,
Doc3ActiviteListeSave, Doc3ActiviteDetailSave,
Doc3EnseignantListSave, Doc3EnseignantDetailSave,
)
doc3 = lire_document_3(org_id)
# doc3 est None si Doc 1 et Doc 2 ne sont pas encore approuvés
liste_save = Doc3ActiviteListeSave(activite=[
Doc3ActiviteDetailSave(
coNumBranche=1,
noAnneeEtude="1",
enseignantListe=Doc3EnseignantListSave(enseignant=[
Doc3EnseignantDetailSave(
coNumAttribution=1,
noMatEns="28901061314",
teStatut="T",
nbPeriodesAttribuees=52.0,
)
]),
),
])
doc3 = modifier_document_3(org_id, liste_save)
SEPS — Recherche et enregistrement d'étudiants
Prérequis :
pip install pyetnic[seps]+ certificat.pfxfourni par ETNIC (IAM-PROD). Ces services fonctionnent uniquement en production (ws.etnic.be).
from pyetnic.seps import (
rechercher_etudiants, lire_etudiant,
enregistrer_etudiant, modifier_etudiant,
EtudiantDetailsSave, SepsNaissanceSave, SepsAdresseSave,
NissMutationError,
)
# Recherche par nom
etudiants = rechercher_etudiants(nom="DUPONT", prenom="Jean")
for e in etudiants:
details = e.rnDetails or e.cfwbDetails
if details:
print(e.cfNum, details.nom, details.prenom, details.naissance.date if details.naissance else "")
# Recherche par NISS (avec gestion de mutation)
try:
etudiants = rechercher_etudiants(niss="850101-123-45")
except NissMutationError as e:
etudiants = rechercher_etudiants(niss=e.nouveau_niss)
# Lecture par numéro CF (format : [0-9]{1,10}-[0-9]{2})
etudiant = lire_etudiant("12345678-01")
if etudiant:
rn = etudiant.rnDetails
print(rn.nom, rn.prenom, rn.niss)
# Enregistrement d'un étudiant (mode DETAILS = recherche par identité)
details = EtudiantDetailsSave(
nom="DUPONT",
prenom="Jean",
sexe="M",
naissance=SepsNaissanceSave(date="1985-01-15", codePays="BE"),
)
etudiant = enregistrer_etudiant("DETAILS", etudiant_details=details)
# Modification d'un étudiant existant
etudiant = modifier_etudiant("12345678-01", etudiant_details=EtudiantDetailsSave(
adresse=SepsAdresseSave(rue="Rue de la Paix", codePostal="1000", codePays="BE"),
))
SEPS — Inscriptions
from pyetnic.seps import (
rechercher_inscriptions, enregistrer_inscription, modifier_inscription,
InscriptionInputDataSave, InscriptionInputSave, SepsUESave,
)
# Recherche des inscriptions d'un étudiant
inscriptions = rechercher_inscriptions(cf_num="12345678-01", annee_scolaire=2024)
for i in inscriptions:
print(i.cfNum, i.anneeScolaire, i.statut, i.ue.noAdministratif if i.ue else "")
# Enregistrement d'une nouvelle inscription
data = InscriptionInputDataSave(
cfNum="12345678-01",
idEtab=3052,
idImplantation=6050,
codePostalLieuCours="4000",
inscription=InscriptionInputSave(
dateInscription="2024-09-15",
statut="I",
ue=SepsUESave(noAdministratif=455, noOrganisation=1),
),
)
inscription = enregistrer_inscription(inscription_input_data=data)
# Modification d'une inscription existante
inscription = modifier_inscription(inscription_input_data=data)
Workflow métier
Les documents suivent un workflow séquentiel imposé par ETNIC :
Créer organisation → statut "Encodé école"
↓
Inspection approuve → statut "Approuvé"
↓
Doc 1 accessible (lecture/modification/approbation)
↓
Doc 2 accessible (lecture/modification)
↓
Doc 1 ET Doc 2 approuvés → Doc 3 accessible
Règles de blocage :
- Doc 1 : inaccessible si l'organisation est "Encodé école"
- Doc 3 : nécessite que Doc 1 et Doc 2 soient approuvés (erreur ETNIC 20102)
- L'approbation de l'organisation est effectuée par l'inspection, pas par l'école
Modèles de données
Identifiant d'organisation
from pyetnic.eprom import OrganisationId
org_id = OrganisationId(
anneeScolaire="2024-2025", # ex. "2024-2025"
etabId=3052, # identifiant établissement
numAdmFormation=455, # numéro administratif de la formation
numOrganisation=1, # numéro d'organisation (attribué par le serveur à la création)
# implId : présent dans les réponses serveur, NE PAS inclure dans les requêtes
)
Important :
implIdest retourné par le serveur mais ne doit pas être envoyé dans les requêtes Lire/Modifier/Supprimer. Seulcreer_organisationaccepteimpl_id.
Vue d'une organisation
OrganisationApercu (retourné par lister_formations) contient les statuts des 4 documents :
| Champ | Description |
|---|---|
statutDocumentOrganisation |
Statut de l'organisation elle-même |
statutDocumentPopulationPeriodes |
Statut du Document 1 |
statutDocumentDroitsInscription |
Statut du Document droits d'inscription |
statutDocumentAttributions |
Statut du Document 3 |
Organisation (retourné par lire_organisation) hérite de OrganisationApercu et ajoute les champs métier complets. Note : les champs statutDocument* sont toujours None dans Organisation — ils ne sont disponibles que via lister_formations.
Gestion des erreurs
Par défaut, les fonctions EPROM retournent None (ou FormationsListeResult(success=False) pour lister_formations*) en cas d'échec serveur, pour préserver la compatibilité ascendante.
Pour du nouveau code, préférer le mode strict qui lève des exceptions typées :
from pyetnic import strict_errors, EtnicBusinessError, EtnicDocumentNotAccessibleError
from pyetnic.eprom import lire_document_3
with strict_errors():
try:
doc3 = lire_document_3(org_id)
except EtnicDocumentNotAccessibleError as e:
print(f"Doc 3 pas encore accessible : {e}")
except EtnicBusinessError as e:
print(f"Erreur ETNIC {e.code} : {e.description}")
Le mode strict peut aussi être activé globalement :
from pyetnic import Config
Config.RAISE_ON_ERROR = True
Le flag est stocké dans un ContextVar : il est donc sûr en contexte multi-threads et asyncio (chaque thread/tâche voit sa propre valeur).
Le comportement par défaut (retour de None) deviendra « lever » en version 0.2.0.
Les services SEPS lèvent déjà des exceptions typées (SepsEtnicError et sous-classes) et ne sont pas affectés par ce flag.
Hiérarchie des exceptions EPROM :
EtnicError
├── EtnicTransportError # erreurs réseau/SOAP (alias historique : SoapError)
└── EtnicBusinessError # refus serveur (success=False)
├── EtnicDocumentNotAccessibleError # code 20102
├── EtnicNotFoundError # code 00009
└── EtnicValidationError # entrée invalide
Codes d'erreur ETNIC courants :
| Code | Description |
|---|---|
| 00009 | Aucun enregistrement trouvé |
| 20102 | Doc 1 et Doc 2 doivent être approuvés pour accéder au Doc 3 |
| SECU-0104 | Certificat X509 non enregistré dans l'annuaire (SEPS en dev) |
Structure du projet
pyetnic/
├── __init__.py # Point d'entrée : from . import eprom, seps
├── eprom/
│ └── __init__.py # Namespace public EPROM (fonctions + modèles)
├── seps/
│ └── __init__.py # Namespace public SEPS (fonctions + modèles)
├── cli.py # CLI : commande init-config
├── config.py # Configuration (.env, endpoints SOAP)
├── soap_client.py # SoapClientManager (zeep + WSSE/X509)
├── services/ # Implémentation interne
│ ├── models.py # Tous les dataclasses
│ ├── formations_liste.py
│ ├── organisation.py
│ ├── document1.py
│ ├── document2.py
│ ├── document3.py
│ ├── seps.py
│ ├── enregistrer_etudiant.py
│ └── inscriptions.py
└── resources/
├── EPROM_Formations_Liste_2.0/
├── EPROM_Formation_Organisation_7.0/
├── EPROM_Formation_Population_1.0/
├── EPROM_Formation_Periodes_1.0/
├── EPROM_Document_3_1.0/
├── SEPS_Recherche_Etudiants_2.1/
├── SEPS_Enregistrer_Etudiant_2.1/
├── SEPS_Enregistrer_Inscription_2.1/
└── SEPS_Recherche_Inscriptions_2.1/
Tests
# Tous les tests (mock + intégration)
pytest tests/
# Mock uniquement (sans credentials)
pytest tests/ -k "mock"
Les tests d'intégration nécessitent un .env valide. Ils skipent automatiquement si les credentials sont absents ou si le document n'est pas accessible dans l'environnement courant.
Environnements ETNIC :
dev→services-web.tq.etnic.be/ws-tq.etnic.be(test, SSL non vérifié)prod→services-web.etnic.be/ws.etnic.be(production, SSL vérifié)
Dépendances
| Package | Usage |
|---|---|
| zeep | Client SOAP |
| python-dotenv | Chargement .env |
| requests | Transport HTTP |
| cryptography | Extraction PFX en mémoire (SEPS) |
| openpyxl | Export Excel |
| xmlsec (extra seps) | Signature XML X509 |
Licence
MIT — voir LICENSE
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyetnic-0.1.0b1.tar.gz.
File metadata
- Download URL: pyetnic-0.1.0b1.tar.gz
- Upload date:
- Size: 67.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
83e70f4ae83271afccfda1557c758b7e3244f1dd09ff242e0080341898f3d3f0
|
|
| MD5 |
a232fba9a17042d4a4d5a1d837546773
|
|
| BLAKE2b-256 |
5b149758fb53f898f6209aef31e7d8fcfe088154cac2ec7dabb0a6a9b1426c25
|
Provenance
The following attestation bundles were made for pyetnic-0.1.0b1.tar.gz:
Publisher:
publish-pypi.yml on Lapin-Blanc/pyetnic
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyetnic-0.1.0b1.tar.gz -
Subject digest:
83e70f4ae83271afccfda1557c758b7e3244f1dd09ff242e0080341898f3d3f0 - Sigstore transparency entry: 1702498037
- Sigstore integration time:
-
Permalink:
Lapin-Blanc/pyetnic@ccdb8a931c79906bfa7b1bf845a5d9fcbde8b3fb -
Branch / Tag:
refs/tags/v0.1.0b1 - Owner: https://github.com/Lapin-Blanc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@ccdb8a931c79906bfa7b1bf845a5d9fcbde8b3fb -
Trigger Event:
push
-
Statement type:
File details
Details for the file pyetnic-0.1.0b1-py3-none-any.whl.
File metadata
- Download URL: pyetnic-0.1.0b1-py3-none-any.whl
- Upload date:
- Size: 107.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
29dcbedf066b9c68e7724260d9a7dc4e0c7b44e25de30021c868b6a597f8992a
|
|
| MD5 |
35e3554a28977882cf4675a9678309e5
|
|
| BLAKE2b-256 |
3ff238c0f0b483431d9095854c5e71289222c61bb5ffdecb615ac50187109a43
|
Provenance
The following attestation bundles were made for pyetnic-0.1.0b1-py3-none-any.whl:
Publisher:
publish-pypi.yml on Lapin-Blanc/pyetnic
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyetnic-0.1.0b1-py3-none-any.whl -
Subject digest:
29dcbedf066b9c68e7724260d9a7dc4e0c7b44e25de30021c868b6a597f8992a - Sigstore transparency entry: 1702498048
- Sigstore integration time:
-
Permalink:
Lapin-Blanc/pyetnic@ccdb8a931c79906bfa7b1bf845a5d9fcbde8b3fb -
Branch / Tag:
refs/tags/v0.1.0b1 - Owner: https://github.com/Lapin-Blanc
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@ccdb8a931c79906bfa7b1bf845a5d9fcbde8b3fb -
Trigger Event:
push
-
Statement type: