pronotepy Python API Wrapper

repository·master·Indexed 42 days ago

https://github.com/bain3/pronotepy

A Python API wrapper for the PRONOTE student administration service, designed for student, parent, and 'vie scolaire' accounts. It provides programmatic access to school data including grades, absences, homework, and schedules without relying on the official HYPERPLANNING API. Features include support for ENT authentication, QR code-based login, and a comprehensive set of data classes for academic entities.

Tokens
8.3K
Snippets
23
Records
35
Agent score
53%

What's inside pronotepy

  1. Introduction to pronotepy

    master

    pronotepy is a Python API wrapper for the PRONOTE student administration service. It allows developers to interact with PRONOTE data programmatically.

    Supported account types:

    • Student accounts
    • Parent accounts
    • Vie-scolaire accounts (partial support)

    Important Note: This project does not use the HYPERPLANNING API provided by PRONOTE; it uses its own implementation to interface with the service.

  2. Access PRONOTE data models

    master
    The pronotepy package provides a comprehensive set of data classes that represent the various entities and information retrieved from the PRONOTE platform. These classes are used to structure the data returned by the API, allowing for type-safe access to school-related information.
  3. Identify the PRONOTE root URL

    master
    The PRONOTE root is the base URL of the instance. You can determine it by looking at your browser URL when connected to a space (espace). For example, if your URL is https://your-host.com/pronote/eleve.html, the root is https://your-host.com/pronote/.
  4. Handle session expiration and object renewal

    master

    PRONOTE changes object IDs every session. While old objects (like pronotepy.Lesson) retain their data (description, subject), calling methods that request new data from PRONOTE will raise a pronotepy.ExpiredObject exception.

    To build long-running bots, use client.session_check() to detect if the connection needs refreshing. If it returns True, you must re-fetch your objects (e.g., re-calling client.lessons(...)) to obtain renewed instances.

    import pronotepy
    import datetime
    from time import sleep
    
    client = pronotepy.Client('https://demo.index-education.net/pronote/eleve.html', 
                              username='demonstration', 
                              password='pronotevs')
    
    if not client.logged_in:
        exit()
    
    # Initial object fetch
    lesson = client.lessons(client.start_day + datetime.timedelta(days=1))[0]
    
    while True:
        # Check if session expired and refresh if necessary
        if client.session_check():
            # Re-fetch the object to get a renewed instance
            lesson = client.lessons(client.start_day + datetime.timedelta(days=1))[0]
            print("Session reinitialised and object renewed.")
    
        print(lesson.content)
        sleep(7200)
  5. Compress and Encrypt `donneesSec`

    master

    Data compression is applied to the contents of donneesSec before encryption.

    Compression Steps:

    1. Convert the JSON data to a UTF-8 string.
    2. Compress the string using zlib (compression level 6, using raw compression without headers).
    3. If not encrypting (i.e., sCrA: true): Convert the raw compressed output to a hex string. This hex string is the donneesSec value.
  6. Encrypt data for `donneesSec`

    master

    When preparing the donneesSec payload, only the contents of that field are encrypted.

    Encryption Workflow:

    1. Prepare Input:
      • If data is compressed, use the compressed bytes.
      • If data is uncompressed, convert the data to a JSON string and use the UTF-8 bytes of that string.
    2. Encrypt: Use the same encryption method used for numeroOrdre.
    3. Format: Convert the resulting output to a hex string. (Note: The web client typically uses uppercase hex, though it may not be strictly required).
  7. Choose the correct client for your user type

    master

    Pronotepy provides three specialized client classes depending on the type of user account you are accessing. All clients automatically attempt to log in upon instantiation.

    • Client: Use this for standard student accounts.
    • ParentClient: Use this for parent accounts. It includes the additional method set_child to switch between different children linked to the account.
    • VieScolaireClient: Use this for 'vie scolaire' (school life) administrative accounts, which provide access to a different set of resources.

    All clients inherit from ClientBase, which handles the underlying login logic and raw communication with the PRONOTE server.

    import pronotepy
    
    # For a student
    client = pronotepy.Client(url, username='...', password='...')
    
    # For a parent
    parent_client = pronotepy.ParentClient(url, username='...', password='...')
    parent_client.set_child('child_id')
    
    # For school administration
    admin_client = pronotepy.VieScolaireClient(url, username='...', password='...')
  8. Log in with username and password

    master

    You can initialize a Client directly with a username and password.

    Requirements:

    • The URL must be a direct link to your PRONOTE instance (e.g., ending in eleve.html or parent.html) and must not include query parameters.
    • If you are logging in through an ENT (Environnement Numérique de Travail), you must pass an ENT-specific function (e.g., ac_reunion from pronotepy.ent) to the ent parameter to fetch cookies correctly.

    High Security Accounts: If your account requires a PIN or recognizes devices, provide account_pin, device_name, and client_identifier. Once logged in, you can retrieve the client_identifier from the client instance to use in future sessions.

    import pronotepy
    from pronotepy.ent import ac_reunion
    
    # Standard login via ENT
    client = pronotepy.Client(
        'https://demo.index-education.net/pronote/eleve.html',
        username='demonstration',
        password='pronotevs',
        ent=ac_reunion
    )
    
    # Login for accounts with higher security (PIN/Device recognition)
    client = pronotepy.Client(
        'https://demo.index-education.net/pronote/eleve.html',
        username='demonstration',
        password='pronotevs',
        account_pin="1111",
        device_name="pronotepy",
        client_identifier=None
    )
    
    # Retrieve identifier to use for future recognized device logins
    identifier = client.client_identifier
  9. Test the pronotepy package

    master

    You can run a self-test of the package using the internal test module. Note that this uses a demo version of PRONOTE, so it may not exercise every possible function or edge case.

    python -m pronotepy.test_pronotepy
  10. Perform a QR code login flow

    master

    To implement a persistent QR code login, follow these steps:

    1. Initial Request: Request the URL with ?login=true (e.g., /pronote/mobile.espace.html?login=true).
    2. Identify Application: Generate a random UUID to identify your application. This UUID must remain constant across all logins for that user. Send this in the Identification request as uuidAppliMobile.
    3. Handle Authentication: In the Authentification response, you will receive a new password token in the field jetonConnexionAppliMobile.
    4. Subsequent Logins: For future sessions, use the original login as the username, the jetonConnexionAppliMobile token as the password, and the same uuidAppliMobile used previously.
  11. Serialize data to JSON

    master

    Pronotepy objects can be converted to Python dictionaries using the .to_dict() method. This is useful for JSON serialization.

    Because the built-in json module cannot serialize datetime, date, or timedelta objects, you must provide a custom default serializer function when calling json.dumps().

    import pronotepy
    import datetime
    import json
    
    client = pronotepy.Client('https://demo.index-education.net/pronote/eleve.html', 
                              username='demonstration', 
                              password='pronotevs')
    
    def serializer(obj):
        if isinstance(obj, (datetime.datetime, datetime.date, datetime.timedelta)):
            return str(obj)
        raise TypeError(f"Type {type(obj)} not serializable")
    
    for period in client.periods:
        # include_properties=True ensures properties like .grades are included
        # exclude allows you to skip specific keys like 'punishments'
        serialized = period.to_dict(include_properties=True, exclude={"punishments", })
        print(json.dumps(serialized, default=serializer, indent=2))