Welcome, developer! This is the definitive guide to the necta-fetcher Python package, expertly crafted by Someless Ado. This library is your key to unlocking programmatic access to student examination results from the NECTA (National Examinations Council of Tanzania) portal
necta-fetcher is designed to shield you from the intricate details of web scraping, such as managing HTTP sessions, handling CSRF (Cross-Site Request Forgery) tokens, and making direct API calls. This allows you to seamlessly integrate NECTA results into your applications, data analysis pipelines, or any other Python-based projects.
Getting necta-fetcher up and running in your Python environment is a simple process facilitated by pip, Python's standard package installer.
Once necta-fetcher is officially published on the Python Package Index (PyPI), you can install it with a single command:
pip install necta-fetcher
It's good practice to ensure your pip installer is up-to-date: pip install --upgrade pip.
If you have a local source archive file (e.g., necta_fetcher-0.1.0.tar.gz) or a pre-built wheel file (.whl):
# Example for a .tar.gz source distribution
pip install /path/to/your/necta_fetcher-0.1.0.tar.gz
# Example for a .whl (wheel) binary distribution
pip install /path/to/your/necta_fetcher-0.1.0-py3-none-any.whl
Remember to replace /path/to/your/ with the correct local file path.
If you're working with the package's source code (e.g., after cloning its Git repository):
# Navigate to the project's root directory (where setup.py is)
cd /path/to/necta_fetcher_project_root
# Standard install from source
pip install .
For active development on the necta-fetcher package itself, an "editable" install is highly beneficial. This creates a link to your source code, meaning any changes you make are immediately usable without reinstalling:
pip install -e .
Isolating your project dependencies is crucial for stable and reproducible Python development. Virtual environments achieve this.
# Step 1: Create a new virtual environment
python -m venv my_app_env # Or python3 -m venv my_app_env
# Step 2: Activate the environment
# On macOS/Linux:
source my_app_env/bin/activate
# On Windows (PowerShell):
my_app_env\Scripts\Activate.ps1
# On Windows (Command Prompt):
my_app_env\Scripts\activate.bat
# Step 3: Install necta-fetcher (and other dependencies) within this isolated environment
pip install necta-fetcher
NectaClient - Your Primary Interface
The central nervous system of the necta-fetcher package is the NectaClient class. All interactions with the NECTA portal are orchestrated through an instance of this class.
To start, import the NectaClient and create an instance.
from necta_fetcher import NectaClient
# Create an instance of the client.
# For version 0.1.0, authentication details are managed internally by the package.
# Future versions may support explicit credential passing, e.g.:
# client = NectaClient(email="your_portal_email@example.com", password="your_portal_password")
client = NectaClient()
You can also customize the User-Agent HTTP header sent by the client. This can be useful for identifying your application if the NECTA portal administrators monitor traffic.
client = NectaClient(user_agent="MyUniversityResultsAggregator/2.1 (admin@myuniversity.edu)")
A Note on Authentication (v0.1.0): The current version (0.1.0) of NectaClient is configured with a specific set of built-in credentials for its operations. For production, enterprise, or any scenario requiring distinct user authentication, ensure you are utilizing a version of the library that explicitly permits secure, user-defined credential input. This documentation will be updated when such features are available.
fetch_student_results()
The flagship method for acquiring examination results is client.fetch_student_results(). This powerful method encapsulates the entire workflow: ensuring the client is logged in (performing login if necessary), refreshing security tokens, making the API request, and processing the response.
NectaClient.fetch_student_results(index_string: str, year: str, exam_level: str) -> dict
| Parameter | Python Type | Detailed Description | Example Values |
|---|---|---|---|
index_string |
str |
The student's complete and official examination index number. The package is generally case-insensitive to the prefix (S/P), but it's good practice to use uppercase. The format typically follows "S####-####" or "P####-####". |
"S1143-0115""P0101-0023"
|
year |
str |
The year the student sat for or completed the specified examination level. This must be provided as a string (e.g., "2022", not the integer 2022). |
"2022""2018"
|
exam_level |
str |
The precise code for the examination level. This value is case-sensitive and must correspond to the identifiers used by the NECTA portal's API. Commonly used levels include:
|
"CSEE""ACSEE"
|
Upon successful execution and finding the student's results, this method returns a Python dict (dictionary). This dictionary is a direct representation of the "data" portion of the JSON response from the NECTA API for the specific student.
This method can raise various exceptions derived from NectaError if any part of the process fails. Comprehensive error handling is crucial (see Chapter 4).
from necta_fetcher import NectaClient, NectaError, NectaStudentNotFoundError
import pprint # For visually appealing dictionary printing
# 1. Initialize the NectaClient
client = NectaClient()
# 2. Define the student query parameters
student_index = "S1143-0115" # A known working example from previous tests
examination_year = "2022"
examination_level = "CSEE"
print(f"š Initiating NECTA results fetch for:")
print(f" Index Number: {student_index}")
print(f" Year: {examination_year}")
print(f" Level: {examination_level}")
print("-" * 50)
try:
# 3. Call the fetch_student_results method
# This single call handles login, token management, and the API request.
results_dictionary = client.fetch_student_results(
index_string=student_index,
year=examination_year,
exam_level=examination_level
)
# 4. Process the successful response
print("\nš Results Retrieved Successfully!\n")
print("Raw Python Dictionary Output:")
pprint.pprint(results_dictionary, indent=2, width=100) # Pretty print
# 5. Accessing and displaying specific data fields
print("\n--- Detailed Breakdown ---")
full_name = f"{results_dictionary.get('first_name', 'N/A')} {results_dictionary.get('middle_name', '')} {results_dictionary.get('last_name', 'N/A')}".strip()
print(f"Student Name: {full_name}")
print(f"Index Number: {results_dictionary.get('index_number', 'N/A')}")
print(f"Overall Division: {results_dictionary.get('division', 'N/A')}")
print(f"Total Points: {results_dictionary.get('points', 'N/A')}")
# Accessing subjects (which is a list of dictionaries)
subjects_list = results_dictionary.get('subjects', [])
if subjects_list:
print("\nš Subject Performance:")
for subject_details in subjects_list:
subject_name = subject_details.get('subject_name', 'Unknown Subject')
grade = subject_details.get('grade', 'N/A')
print(f" - {subject_name:<25}: {grade}") # Formatted output
else:
print("\nā¹ļø No detailed subject information was found in the results.")
except NectaStudentNotFoundError:
print(f"\nā ļø Error: The student with index '{student_index}' for year {examination_year}, level {examination_level} was NOT FOUND on the NECTA portal.")
print(" Please verify the accuracy of the entered details or try alternative parameters.")
except NectaLoginError as e:
print(f"\nš„ Login Error: Authentication with the NECTA portal failed. Details: {e}")
except NectaTokenError as e:
print(f"\nš Token Error: An issue occurred with security (CSRF) token management. Details: {e}")
except NectaResultError as e:
print(f"\nš Result Error: A problem occurred while fetching or interpreting the results data. Details: {e}")
except NectaRequestError as e:
print(f"\nš Request Error: A network connectivity or HTTP request issue was encountered. Details: {e}")
except NectaError as e: # Catch-all for other necta_fetcher specific errors
print(f"\nāļø Necta-Fetcher Library Error: {e}")
except Exception as e: # Catch any other unexpected Python errors
print(f"\nš„ An Unforeseen System Error Occurred: {e}")
import traceback
traceback.print_exc() # Provides full traceback for debugging
finally:
print("-" * 50)
print("Fetch attempt concluded.")
The results_dictionary you receive from a successful call to fetch_student_results() will typically conform to a structure like the following (based on observed API responses). The exact fields and their presence can vary.
{
"index_number": "S1143-0115", // The student's unique index
"first_name": "SAMWEL", // Student's first name
"middle_name": "N", // Student's middle name (can be empty or N/A)
"last_name": "ADONIA", // Student's last name
"sex": "MALE", // Gender (may not always be present)
"school_name": "UBUNGO ISLAMIC HIGH SCHOOL", // Name of the examination center/school
"division": "III", // Overall division achieved
"points": "24", // Total points scored
"subjects": [ // A list of subjects taken
{
"subject_name": "BASIC MATHEMATICS",
"grade": "D"
},
{
"subject_name": "BIOLOGY",
"grade": "D"
},
{
"subject_name": "CHEMISTRY",
"grade": "C"
},
{
"subject_name": "PHYSICS",
"grade": "C"
},
{
"subject_name": "ENGLISH LANGUAGE",
"grade": "C"
},
{
"subject_name": "KISWAHILI",
"grade": "C"
},
{
"subject_name": "PHYSICAL EDUCATION",
"grade": "D"
},
{
"subject_name": "GEOGRAPHY",
"grade": "D"
},
{
"subject_name": "HISTORY",
"grade": "D"
},
{
"subject_name": "CIVICS",
"grade": "F"
}
// ... other subjects may be listed
],
"center_number": "S1143", // Code for the examination center
"exam_type": "CSEE", // Type of examination (e.g., CSEE, ACSEE)
"exam_year": "2022" // Year of examination
// Other fields like 'status', 'remarks' might also be present.
}
necta-fetcher package endeavors to return the primary "data" payload from the NECTA API's JSON response with minimal modification. If the NECTA API alters its response schema (e.g., adds new fields, renames existing ones, or changes data types), the dictionary returned by this package will directly reflect those modifications. It is always advisable to inspect the structure of the returned dictionary, especially if you encounter unexpected behavior or missing data after a portal update.
Effective error handling is paramount for building resilient applications. The necta-fetcher package equips you with a clear hierarchy of custom exceptions, all descending from the base NectaError. This allows for granular control over how your application responds to various failure scenarios.
NectaError (Base Exception for the package)
Catch this if you want a general handler for any issue originating from necta-fetcher.
NectaLoginError:
Signifies a failure during the authentication process. This can occur due to:
NectaTokenError:
Indicates a problem related to CSRF (Cross-Site Request Forgery) tokens. These tokens are essential for security. Errors can arise if:
NectaRequestError:
This exception covers broader network communication problems or unexpected HTTP error responses from the NECTA portal during any non-login/non-result-specific API interaction. Examples include:
NectaResultError (Base for issues specific to fetching/processing results)
This is raised for problems occurring after a successful login and token acquisition, specifically during the attempt to fetch or interpret student results.
NectaStudentNotFoundError:
A very common and important exception. It is raised specifically when:
"success": false response with a "not found" message.NectaResultError instances): These can occur if:
"success": false with a non-"not found" error message)."data" field, or the "data" field is not a dictionary when a specific student's results are anticipated).
By structuring your try...except blocks from most specific to most general (e.g., catching NectaStudentNotFoundError before NectaResultError, and NectaResultError before NectaError), you can tailor your application's response to different failure modes.
# Recommended robust error handling pattern
client = NectaClient()
try:
student_data = client.fetch_student_results(
index_string="SXXXX-YYYY",
year="YYYY",
exam_level="LEVEL"
)
# ... process student_data ...
print("Results processed successfully.")
except NectaStudentNotFoundError as e_student_not_found:
print(f"Notification: Results for the specified student were not found. Details: {e_student_not_found}")
# Action: Inform the user, log the query, suggest checking inputs.
except NectaLoginError as e_login:
print(f"Critical Error: Login to NECTA portal failed. Details: {e_login}")
# Action: Alert administrator, check system's network, halt operations requiring login.
except NectaTokenError as e_token:
print(f"Warning: CSRF token issue encountered. The portal's security mechanism might have changed. Details: {e_token}")
# Action: May require a library update or manual investigation of portal changes.
except NectaResultError as e_result: # Catches other result-related issues not covered by StudentNotFound
print(f"Error: There was an issue obtaining or interpreting the results data from NECTA. Details: {e_result}")
# Action: Log the error and the problematic query, potentially retry later.
except NectaRequestError as e_request:
print(f"Network/Server Error: Communication with the NECTA portal failed. Details: {e_request}")
# Action: Check internet connectivity, inform user to try again later.
except NectaError as e_necta_base: # Fallback for any other necta-fetcher specific error
print(f"Library Error: A general error occurred within the necta-fetcher package. Details: {e_necta_base}")
except requests.exceptions.RequestException as e_requests_lib: # Catch underlying requests library errors if not wrapped
print(f"Low-Level Network Error: An error occurred in the underlying HTTP requests library. Details: {e_requests_lib}")
# This is more for debugging library issues.
except Exception as e_unexpected: # General Python errors
print(f"System Error: An unexpected error occurred in the application. Details: {e_unexpected}")
# Action: Log detailed traceback for developer review.
# import traceback
# traceback.print_exc()
You can easily ascertain the installed version of the necta-fetcher package within your Python code. This is useful for logging, debugging, or conditional logic based on package capabilities.
import necta_fetcher
current_version = necta_fetcher.__version__
print(f"You are currently using necta-fetcher version: {current_version}")
When you invoke client.fetch_student_results(), the NectaClient executes a sequence of operations:
_token). tags.year, number (which is the full student index string), level, and the freshly acquired action CSRF token.Content-Type: application/x-www-form-urlencoded, X-Requested-With: XMLHttpRequest, and potentially an X-XSRF-TOKEN header if the corresponding cookie is present)..../candidates/nectaResult)."success": true flag and a "data" field)."data" field corresponds to the queried student (by matching index numbers) and is not merely a placeholder (e.g., by checking if first_name is "N/A" and subjects list is empty).
The NectaClient leverages an internal requests.Session() object. This powerful feature from the requests library automatically manages cookies (such as session identifiers and XSRF tokens) across the multiple HTTP requests involved in the workflow, maintaining the authenticated state with the NECTA portal.
Here is a self-contained Python script that you can copy, paste, and run directly in your terminal (after installing the necta-fetcher package). This example demonstrates a common use case: prompting the user for student details and displaying the fetched results.
#!/usr/bin/env python3
# Filename: necta_terminal_app.py
import re
import pprint
from necta_fetcher import (
NectaClient,
__version__ as necta_fetcher_version,
NectaError,
NectaStudentNotFoundError,
NectaLoginError,
NectaTokenError,
NectaResultError,
NectaRequestError
)
def get_user_input(prompt_message: str, validation_regex: str = None, error_message: str = "Invalid input.") -> str:
"""Generic function to get and validate user input."""
while True:
user_val = input(f"{prompt_message}: ").strip()
if not validation_regex or re.match(validation_regex, user_val, re.IGNORECASE):
return user_val
print(f"ā Error: {error_message}")
def main_terminal_application():
"""Runs the interactive terminal application to fetch NECTA results."""
print("=" * 70)
print(f" NECTA Results Interactive Terminal Client")
print(f" (Powered by necta-fetcher v{necta_fetcher_version} by Someless Ado)")
print("=" * 70)
print("This tool uses the 'necta-fetcher' package to retrieve student results.")
print("Ensure the package is installed (`pip install necta-fetcher`).")
print("Note: The current package version uses an internally managed login.\n")
# Get validated inputs
full_index = get_user_input(
prompt_message="Enter Student's Full Index Number (e.g., S1234-0001)",
validation_regex=r"^[SP]\d{4}-\d{4}$",
error_message="Format must be Sxxxx-yyyy or Pxxxx-yyyy (e.g., S1234-0001)."
).upper()
year = get_user_input(
prompt_message="Enter Examination Year (e.g., 2022)",
validation_regex=r"^(20[0-2]\d|199\d)$", # Approx 1990-2029
error_message="Please enter a 4-digit year (e.g., 2022)."
)
exam_level = get_user_input(
prompt_message="Enter Examination Level (CSEE, ACSEE, PSLE)",
validation_regex=r"^(CSEE|ACSEE|PSLE)$",
error_message="Accepted levels are CSEE, ACSEE, or PSLE."
).upper()
print("\n" + "~" * 30)
print(f"ā³ Preparing to fetch results for:")
print(f" Index: {full_index}")
print(f" Year: {year}")
print(f" Level: {exam_level}")
print("~" * 30 + "\n")
# Initialize the NectaClient from the installed package
client = NectaClient()
try:
# Attempt to fetch the results
student_results_data = client.fetch_student_results(
index_string=full_index,
year=year,
exam_level=exam_level
)
print("\n" + "š" * 15 + " RESULTS OBTAINED " + "š" * 15)
print("Student Details:")
pprint.pprint(student_results_data, indent=2, width=100)
except NectaStudentNotFoundError as e:
print(f"\nš« STUDENT NOT FOUND: {e}")
print(" Please ensure all details (index, year, level) are correct and try again.")
except NectaLoginError as e:
print(f"\nš„ LOGIN FAILURE: {e}")
print(" Could not authenticate with the NECTA portal. Check network or system configuration.")
except NectaTokenError as e:
print(f"\nš TOKEN ACQUISITION FAILURE: {e}")
print(" There was an issue with security tokens. The portal might have updated its systems.")
except NectaResultError as e:
print(f"\nš RESULTS PROCESSING ERROR: {e}")
print(" An error occurred while trying to fetch or understand the results data.")
except NectaRequestError as e:
print(f"\nš NETWORK/REQUEST ERROR: {e}")
print(" A problem occurred during communication with the NECTA portal. Check your internet connection.")
except NectaError as e:
print(f"\nāļø NECTA-FETCHER LIBRARY ERROR: {e}")
except Exception as e:
print(f"\nš„ UNEXPECTED SYSTEM ERROR: {e}")
print(" An unhandled error occurred. Please report this with the details below:")
import traceback
traceback.print_exc()
finally:
print("\n" + "=" * 70)
print("Query attempt finished. Run the script again to fetch another result.")
print("=" * 70)
if __name__ == "__main__":
main_terminal_application()
The journey of necta-fetcher is ongoing. Someless Ado and potential future contributors envision several enhancements:
async/await support for non-blocking API calls, beneficial for applications handling many requests concurrently.Feedback, bug reports, feature suggestions, and contributions from the developer community are highly valued. Please refer to the project's official repository (if one is made public) for contribution guidelines or to contact the developer, Someless Ado.
The digital landscape, especially web services and APIs, is constantly evolving. To ensure you have the latest features, bug fixes, and adaptations to any NECTA portal changes, periodically update your installed necta-fetcher package:
pip install --upgrade necta-fetcher