Skip to main content

Access to ETNIC web services through Python

Project description

pyetnic

Tests

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écessite pip 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 .pfx fourni 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 : implId est retourné par le serveur mais ne doit pas être envoyé dans les requêtes Lire/Modifier/Supprimer. Seul creer_organisation accepte impl_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 :

  • devservices-web.tq.etnic.be / ws-tq.etnic.be (test, SSL non vérifié)
  • prodservices-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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyetnic-0.1.0b1.tar.gz (67.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyetnic-0.1.0b1-py3-none-any.whl (107.3 kB view details)

Uploaded Python 3

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

Hashes for pyetnic-0.1.0b1.tar.gz
Algorithm Hash digest
SHA256 83e70f4ae83271afccfda1557c758b7e3244f1dd09ff242e0080341898f3d3f0
MD5 a232fba9a17042d4a4d5a1d837546773
BLAKE2b-256 5b149758fb53f898f6209aef31e7d8fcfe088154cac2ec7dabb0a6a9b1426c25

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyetnic-0.1.0b1.tar.gz:

Publisher: publish-pypi.yml on Lapin-Blanc/pyetnic

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for pyetnic-0.1.0b1-py3-none-any.whl
Algorithm Hash digest
SHA256 29dcbedf066b9c68e7724260d9a7dc4e0c7b44e25de30021c868b6a597f8992a
MD5 35e3554a28977882cf4675a9678309e5
BLAKE2b-256 3ff238c0f0b483431d9095854c5e71289222c61bb5ffdecb615ac50187109a43

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyetnic-0.1.0b1-py3-none-any.whl:

Publisher: publish-pypi.yml on Lapin-Blanc/pyetnic

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page