Skip to main content

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.


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, SepsSpecificiteSave,
    SepsDroitInscriptionSave, SepsAdmissionSave, SepsSanctionSave,
)

# 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

Toutes les fonctions de service retournent None (plutôt qu'une exception) si :

  • Le serveur répond avec success: False (accès refusé, document non existant, workflow non respecté, etc.)
  • Le document n'est pas accessible selon le workflow métier

Les erreurs réseau et SOAP sont encapsulées dans SoapError et propagées.

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
└── 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/

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é)

Release PyPI (Trusted Publishing)

1) Configurer le Trusted Publisher sur PyPI

Dans PyPI (pyetnic), aller dans Manage project -> Publishing -> Add a new publisher puis renseigner :

  • Owner: Lapin-Blanc
  • Repository name: pyetnic
  • Workflow name: publish-pypi.yml
  • Environment name: pypi

Le workflow est dans .github/workflows/publish-pypi.yml et utilise OIDC via pypa/gh-action-pypi-publish@release/v1.

2) Créer l'environnement GitHub pypi

Dans GitHub : Settings -> Environments -> New environment -> nommer pypi.

Optionnel : ajouter des protection rules (reviewers, branche/tag rules) selon votre politique de release.

3) Bump version

Mettre à jour la version dans :

  • pyproject.toml ([project].version)
  • pyetnic/__init__.py (__version__)

Exemple pour cette release : 0.0.9.

4) Tagger et publier

git add pyproject.toml pyetnic/__init__.py README.md .github/workflows/publish-pypi.yml
git commit -m "release: v0.0.9"
git tag v0.0.9
git push origin main
git push origin v0.0.9

Le workflow GitHub Actions :

  • se déclenche sur tag v*
  • vérifie que le tag est exactement v<version pyproject.toml>
  • build le package (python -m build)
  • vérifie les artefacts (twine check dist/*)
  • publie sur PyPI via Trusted Publishing (OIDC)

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.0.10.tar.gz (80.0 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.0.10-py3-none-any.whl (104.4 kB view details)

Uploaded Python 3

File details

Details for the file pyetnic-0.0.10.tar.gz.

File metadata

  • Download URL: pyetnic-0.0.10.tar.gz
  • Upload date:
  • Size: 80.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pyetnic-0.0.10.tar.gz
Algorithm Hash digest
SHA256 34cfff23bc2fbff24919cc4bc4654acaa8ec964267243d89187081bbc341d860
MD5 a750117d511e8d5394e43c0758a1c204
BLAKE2b-256 f035097506c7f7d37aa0228b6a8dd50e69b498bb3acdd5cb73a0f465a2c0102b

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyetnic-0.0.10.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.0.10-py3-none-any.whl.

File metadata

  • Download URL: pyetnic-0.0.10-py3-none-any.whl
  • Upload date:
  • Size: 104.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for pyetnic-0.0.10-py3-none-any.whl
Algorithm Hash digest
SHA256 cb5dd0977d02d6ae9cddf8e08306b7149890b2f5df153171ae6c2734ff8a2391
MD5 b9297c290c4f06cf805598a0c307ecce
BLAKE2b-256 ba519c0f24b7ee31542b065b374306fc8c8bf690c756d86bb7dab555427e4fa1

See more details on using hashes here.

Provenance

The following attestation bundles were made for pyetnic-0.0.10-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