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)

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"),
))

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.9.tar.gz (79.2 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.9-py3-none-any.whl (104.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pyetnic-0.0.9.tar.gz
  • Upload date:
  • Size: 79.2 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.9.tar.gz
Algorithm Hash digest
SHA256 f647a3e68baf4c94cd803b87e40f2e07f32602dc316142016d5ee069a352d2e0
MD5 4c03aa0a027a4c8335f1d7fdd806d71d
BLAKE2b-256 3f7cba1eb14d6abe5975418accbca89b06c0833a9acbbd495b1928e1fe569b3f

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: pyetnic-0.0.9-py3-none-any.whl
  • Upload date:
  • Size: 104.1 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.9-py3-none-any.whl
Algorithm Hash digest
SHA256 6c8d896860916d805f75d90619b5631d64c449a62613c81435a4fe744687fa6d
MD5 87ebf8ad7b21f9f3dddcace90c11f011
BLAKE2b-256 9558135c92aacd8b4662c699bf1a78fb4275172fa47dfe389f19664e51ea471a

See more details on using hashes here.

Provenance

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