diff --git a/README.md b/README.md index b5df43f..e295421 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A package for students of Panimalar Engineering College ## Modules -### rollno +## rollno - isvalid(rollno) => boolean - get_dept_code(rollno) => string @@ -12,6 +12,33 @@ A package for students of Panimalar Engineering College - parse(rollno, required) => string (or) int - get_year(rollno) => int +## cgpa + +### Classes and Methods + +- Subject +- Gpa(Subject) +- Cgpa(Gpa) + +#### Subject +This class stores the credit and grade point of a subject. +- credit +- grade point + +#### Gpa + +- addsubject(self, credit: int, grade_point: int) +- calc(self) -> float +- display(self) -> None +- added(self) -> None +- removesubject(self, index: int) -> None + +#### Cpga +- calc(self) +- display(self) -> None +- added(self) -> None +- removesemester(self, index: int) -> None + ## Details @@ -21,7 +48,7 @@ A package for students of Panimalar Engineering College Roll Number in Panimalar follows the system (YEAROFJOIN)YYYY-PEC-(DEPT)DD-(ROLLNO)XXXX -eg) 2021PECCB101 +eg - 2021PECCB101 2021 - Year of Join PEC - Panimalar Engineering College diff --git a/pyproject.toml b/pyproject.toml index eeae459..2126153 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,8 +6,8 @@ build-backend = "setuptools.build_meta" name = "panimalar" version = "0.3.2" authors = [ - { name="The Coding Society", email="codingsociety@psdc.org.in"}, { name="aviiciii (Laaveshwaran Parthiban)", email="laavesh1@gmail.com" }, + { name="The Coding Society", email="codingsociety@psdc.org.in"}, ] description = "A package of function created by and for The Coding Society, Panimalar Engineering College" readme = "README.md" @@ -19,6 +19,8 @@ classifiers = [ ] dependencies = [ "regex>=2022.10.31", + "pytest>=7.2.2", + "texttable>=1.6.4", ] diff --git a/src/cgpa/__init__.py b/src/cgpa/__init__.py new file mode 100644 index 0000000..cbdbf08 --- /dev/null +++ b/src/cgpa/__init__.py @@ -0,0 +1 @@ +from .module import * \ No newline at end of file diff --git a/src/cgpa/module.py b/src/cgpa/module.py new file mode 100644 index 0000000..e097dff --- /dev/null +++ b/src/cgpa/module.py @@ -0,0 +1,290 @@ +# modules +from texttable import Texttable + +class Subject: + """ + This class stores the credit and grade point of a subject. + """ + + # constructor + def __init__(self, credit: int, grade_point: int) -> None: + + # check if the credit and grade point are integers and are not negative + if type(credit) != int: + raise TypeError("Credit should be an integer.") + if type(grade_point) != int: + raise TypeError("Grade Point should be an integer.") + if credit < 0 or grade_point < 0: + raise ValueError("Credit and Grade Point cannot be negative.") + + + self.credit = credit + self.grade_point = grade_point + + def __str__(self) -> str: + return "Credit: " + str(self.credit) + "\nGrade Point: " + str(self.grade_point) + + +class Gpa(Subject): + """ + This class calculates the GPA of a student. + It takes the credit and grade point of each subject as input and returns the GPA. + """ + + # Class variables + # maintains no of subjects added + no_of_subjects = 0 + + # Constructor + def __init__(self) -> None: + self.gpa = 0.0 + self.numerator = 0.0 + self.denominator = 0.0 + self.sub=[] + + # Add a subject + def addsubject(self, credit: int, grade_point: int) -> None: + """ + addsubject(credit, grade_point) + This function adds a subject to the list of subjects. + """ + + + # limits on credit and grade point + + if credit > 8: + raise ValueError("Credit cannot be greater than 8.") + if grade_point > 10: + raise ValueError("Grade Point cannot be greater than 10.") + + + # Add subject to the list + self.sub.append(Subject(credit, grade_point)) + self.no_of_subjects += 1 + + # Calculate GPA + def calc(self) -> float: + """ + calc() + This function calculates the GPA of the student. + """ + + if self.no_of_subjects == 0: + raise ValueError("No subjects added yet. Run gpa.addsubject(credit, grade_point) first.") + + # Check if there is at least one subject with grade point greater than 5 + for i in range(self.no_of_subjects): + greater_than_5 = False + if self.sub[i].grade_point > 5: + greater_than_5 = True + else: + pass + + if not greater_than_5: + raise ValueError("No subjects with grade point greater than 5.") + + # Iterate through the list of subjects and calculate the numerator and denominator + for i in range(self.no_of_subjects): + + # Check if the grade point is less than 5 + if self.sub[i].grade_point < 5: + continue + + # Calculate the numerator and denominator + self.numerator += self.sub[i].credit * self.sub[i].grade_point + self.denominator += self.sub[i].credit + + # Calculate the GPA with the numerator and denominator + self.gpa = self.numerator / self.denominator + + # Check if GPA is valid + if self.gpa > 10 and self.gpa < 0: + print("GPA is not valid. Please check your inputs.") + return + + return self.gpa + + def display(self) -> None: + """ + display() + This function displays the number of subjects and the GPA. + """ + + + # Check if at least one subject is added + if self.no_of_subjects == 0: + raise ValueError("No subjects added yet. Run gpa.addsubject(credit, grade_point) first.") + + + # Check if GPA is calculated + if self.gpa == 0.0: + raise ValueError("No GPA calculated yet. Run gpa.calc() first.") + + + # Display the number of subjects and the GPA + print("No of subjects: ", self.no_of_subjects) + print("GPA: ", self.gpa) + + def added(self) -> None: + """ + added() + This function displays the subjects added. + """ + + table = Texttable() + + table.header(["No.", "Credits", "Grade Points"]) + table.set_cols_dtype(['i', 'i', 'i']) + table.set_cols_align(['r', 'r', 'r']) + + for i in range(self.no_of_subjects): + table.add_row([i+1, self.sub[i].credit, self.sub[i].grade_point]) + + table.set_deco(Texttable.HEADER) + print('Subjects added:') + print(table.draw()) + + def removesubject(self, index: int) -> None: + """ + removesubject(index) + Index values: 1-n + This function removes a subject from the list of subjects. + """ + index = index-1 + # Check if the index is valid + if index > self.no_of_subjects or index < 0: + raise ValueError(f"Index out of range. Input Index: {index+1} Index Region: 0 to {self.no_of_subjects}") + + + # Remove the subject from the list + removed = self.sub.pop(index-1) + self.no_of_subjects -= 1 + print(f'Subject {index + 1} {removed.credit, removed.grade_point} removed successfully.') + + def __repr__(self) -> str: + return "GPA Calculator" + + def __str__(self) -> str: + + if self.no_of_subjects == 0: + return "No subjects added yet. Run gpa.addsubject(credit, grade_point) first." + + return "No of subjects: " + str(self.no_of_subjects) + "GPA: " + str(self.gpa) + + +class Cgpa(Gpa): + """ + This class calculates the CGPA of a student. + It takes the GPA of each semester as input and returns the CGPA. + """ + + # Constructor + def __init__(self): + self.cgpa = 0.0 + self.total_numerator = 0.0 + self.total_denominator = 0 + self.semester = [] + + + # Add a semester gpa + def addsemester(self, gpa: Gpa) -> None: + """ + addsemester(gpa) + This function adds a semester gpa to the list of semester gpa. + """ + + # Check if the input is of type Gpa else raise an error + if not isinstance(gpa, Gpa): + raise TypeError("Input must be of type Gpa.") + + # Check if the gpa is calculatable else raise an error + if gpa.no_of_subjects <= 0: + raise ValueError("No subjects added yet. Run gpa.addsubject(credit, grade_point) first.") + + + + gpa.calc() + self.semester.append(gpa) + + + # Calculate CGPA + def calc(self) -> float: + """ + calc() + This function calculates the CGPA of the student. + """ + + if len(self.semester) == 0: + raise ValueError("No semesters added yet. Run cgpa.addsemester(gpa) first.") + + # Iterate through the list of semester gpa and calculate the numerator and denominator + for i in range(len(self.semester)): + self.total_numerator += self.semester[i].numerator + self.total_denominator += self.semester[i].denominator + + # Calculate the CGPA with the numerator and denominator + self.cgpa = self.total_numerator / self.total_denominator + + # Check if CGPA is valid + if self.cgpa > 10 and self.cgpa < 0: + print("CGPA is not valid. Please check your inputs.") + return + + return self.cgpa + + # Display CGPA + def display(self) -> None: + """ + display() + This function displays the number of semesters and the CGPA. + """ + + if len(self.semester) == 0: + raise ValueError("No semesters added yet. Run cgpa.addsemester(gpa) first.") + if self.cgpa == 0.0: + raise ValueError("No CGPA calculated yet. Run cgpa.calc() first.") + + print("No of semesters: ", len(self.semester)) + print("CGPA: ", self.cgpa) + + def added(self) -> None: + """ + added() + This function displays the semesters added. + """ + + table = Texttable() + + table.header(["No.", "GPA", "\u03A3 GP*C", "\u03A3 Credits"]) + table.set_cols_dtype(['i', 'f', 'i', 'i']) + table.set_cols_align(['r', 'r', 'r', 'r']) + + for i in range(len(self.semester)): + table.add_row([i+1, self.semester[i].gpa, self.semester[i].numerator, self.semester[i].denominator]) + + table.set_deco(Texttable.HEADER) + print('Semesters added:') + print(table.draw()) + + def removesemester(self, index: int) -> None: + """ + removesemester(index) + Index values: 1-n + This function removes a semester from the list of semesters. + """ + index = index-1 + # Check if the index is valid + if index > len(self.semester) or index < 0: + raise ValueError(f"Index out of range. Input Index: {index+1} Index Region: 0 to {len(self.semester)}") + + + # Remove the semester from the list + removed = self.semester.pop(index-1) + print(f'Semester {index+1} {removed.gpa, removed.numerator, removed.denominator} removed successfully.') + + def __repr__(self) -> str: + return "CGPA Calculator" + + def __str__(self) -> str: + return "No of semesters: " + str(len(self.semester)) + "CGPA: " + str(self.cgpa) + diff --git a/tests/test_cgpa.py b/tests/test_cgpa.py new file mode 100644 index 0000000..d7829d2 --- /dev/null +++ b/tests/test_cgpa.py @@ -0,0 +1,415 @@ +import pytest +from src.cgpa import * + + +# Subject class test cases + +def test_subject_class(): + # Test if the subject class is defined + obj = Subject(4, 10) + assert obj.credit == 4 + assert obj.grade_point == 10 + + obj = Subject(3, 9) + assert obj.credit == 3 + assert obj.grade_point == 9 + + # raise an error if the credit is not an integer + with pytest.raises(TypeError): + obj = Subject(3.5, 9) + with pytest.raises(TypeError): + obj = Subject('three', 9) + with pytest.raises(TypeError): + obj = Subject(True, 9) + + # raise an error if the grade_point is not an integer + with pytest.raises(TypeError): + obj = Subject(3, 9.5) + with pytest.raises(TypeError): + obj = Subject(3, 'nine') + with pytest.raises(TypeError): + obj = Subject(3, True) + + # raise an error if the credit and grade point is not negative + with pytest.raises(ValueError): + obj = Subject(-3, 9) + with pytest.raises(ValueError): + obj = Subject(3, -9) + with pytest.raises(ValueError): + obj = Subject(-3, -9) + +# GPA class test cases + +# addsubject function test cases + +def test_addsubject(): + # create an object of the gpa class + obj = Gpa() + + # Test if the addsubject function is defined + obj.addsubject(4, 10) + assert obj.no_of_subjects == 1 + assert obj.sub[0].credit == 4 + assert obj.sub[0].grade_point == 10 + + obj.addsubject(3, 9) + assert obj.no_of_subjects == 2 + assert obj.sub[1].credit == 3 + assert obj.sub[1].grade_point == 9 + + +def test_invalid_credit(): + with pytest.raises(TypeError): + obj = Subject(3.5, 9) + with pytest.raises(TypeError): + obj = Subject('three', 9) + with pytest.raises(TypeError): + obj = Subject(True, 9) + + +def test_invalid_grade_point(): + with pytest.raises(TypeError): + obj = Subject(3, 9.5) + with pytest.raises(TypeError): + obj = Subject(3, 'nine') + with pytest.raises(TypeError): + obj = Subject(3, True) + + +def test_invalid_credit_and_grade_point(): + obj = Gpa() + # raise an error if the credit and grade point is not negative + with pytest.raises(ValueError): + obj.addsubject(-3, 9) + with pytest.raises(ValueError): + obj.addsubject(3, -9) + with pytest.raises(ValueError): + obj.addsubject(-3, -9) + + +def test_credit_greater_than_8(): + obj = Gpa() + + # raise an error if the credit is greater than 8 + with pytest.raises(ValueError): + obj.addsubject(9, 9) + with pytest.raises(ValueError): + obj.addsubject(13, 9) + + +def test_grade_point_greater_than_10(): + obj = Gpa() + + # raise an error if the grade point is greater than 10 + with pytest.raises(ValueError): + obj.addsubject(3, 11) + with pytest.raises(ValueError): + obj.addsubject(3, 15) + +# calc function test cases + +def test_calc_1(): + obj = Gpa() + + # Test if the calc function is defined + obj.addsubject(4, 10) + obj.addsubject(3, 9) + obj.addsubject(3, 8) + obj.addsubject(2, 7) + obj.addsubject(2, 8) + obj.addsubject(1, 9) + obj.addsubject(3, 9) + obj.addsubject(2, 3) + obj.calc() + + assert obj.no_of_subjects == 8 + + assert obj.gpa == 8.722222222222221 + + +def test_calc_2(): + obj = Gpa() + + # Test if the calc function is defined + obj.addsubject(4, 10) + obj.addsubject(3, 10) + obj.addsubject(3, 10) + obj.addsubject(2, 10) + obj.addsubject(2, 10) + obj.addsubject(1, 10) + obj.addsubject(3, 10) + obj.addsubject(2, 10) + obj.addsubject(2, 10) + obj.calc() + + assert obj.gpa == 10 + + +def test_calc_3(): + obj = Gpa() + + # Test if the calc function is defined + obj.addsubject(4, 7) + obj.addsubject(3, 7) + obj.addsubject(3, 7) + obj.addsubject(2, 7) + obj.addsubject(2, 7) + obj.addsubject(1, 7) + obj.addsubject(3, 7) + obj.addsubject(2, 7) + obj.addsubject(2, 7) + obj.calc() + + assert obj.gpa == 7 + + +def test_calc_4(): + obj = Gpa() + + # Test if the calc function is defined + obj.addsubject(2, 8) + obj.addsubject(3, 8) + obj.addsubject(3, 7) + obj.addsubject(4, 8) + obj.addsubject(2, 10) + obj.addsubject(4, 9) + obj.addsubject(4, 9) + obj.addsubject(3, 8) + obj.calc() + + assert obj.gpa == 8.36 + + +def test_calc_with_no_subjects(): + obj = Gpa() + + # Test if the calc function is defined + with pytest.raises(ValueError): + obj.calc() + + assert obj.gpa == 0.0 + +# display function test cases + +def test_display(capsys): + obj = Gpa() + + # Test if the display function is defined + obj.addsubject(4, 10) + obj.addsubject(3, 9) + obj.addsubject(3, 8) + obj.addsubject(2, 7) + obj.addsubject(2, 7) + obj.addsubject(1, 9) + obj.addsubject(3, 8) + obj.addsubject(2, 3) + + # calculate and call the display function + obj.calc() + obj.display() + + # capture the output(prints) + captured = capsys.readouterr() + + # check if the output is correct + assert captured.out == "No of subjects: 8\nGPA: 8.444444444444445\n" + + +def test_display_with_no_subjects(): + obj = Gpa() + + # Test if the display function is defined + with pytest.raises(ValueError): + obj.display() + + assert obj.gpa == 0.0 + + +def test_display_with_no_calc(): + obj = Gpa() + + # Test if the display function is defined + obj.addsubject(4, 10) + obj.addsubject(3, 9) + obj.addsubject(3, 8) + obj.addsubject(2, 7) + obj.addsubject(2, 7) + obj.addsubject(1, 9) + obj.addsubject(3, 8) + obj.addsubject(2, 3) + + with pytest.raises(ValueError): + obj.display() + + assert obj.gpa == 0.0 + + +# gpa class overall test cases +def test_gpa_class(capsys): + obj = Gpa() + + # add subjects + credits = [2, 3, 3, 4, 2, 4, 4, 3] + grade_points = [8, 8, 7, 8, 10, 9, 9, 8] + + for i in range(len(credits)): + obj.addsubject(credits[i], grade_points[i]) + + # check if the number of subjects is correct + assert obj.no_of_subjects == 8 + + # check if subjects are added to list + assert len(obj.sub) == 8 + + # check if subjects are added correctly + for i in range(len(obj.sub)): + assert obj.sub[i].credit == credits[i] + assert obj.sub[i].grade_point == grade_points[i] + + # calculate + obj.calc() + + # display + obj.display() + + # capture the output(prints) + captured = capsys.readouterr() + + # check if the output is correct + assert captured.out == "No of subjects: 8\nGPA: 8.36\n" + + # check if the gpa is correct + assert obj.gpa == 8.36 + + +# cgpa class test cases + + +# addsemester function test cases +def test_addsemester(): + obj = Cgpa() + + gpa_obj = Gpa() + + # add subjects + credits = [2, 3, 3, 4, 2, 4, 4, 3] + grade_points = [8, 8, 7, 8, 10, 9, 9, 8] + + for i in range(len(credits)): + gpa_obj.addsubject(credits[i], grade_points[i]) + + + # Test if the addsemester function is defined + obj.addsemester(gpa_obj) + + # check if the semester is added to the list + assert len(obj.semester) == 1 + + # check if the semester is added correctly + assert obj.semester[0].gpa == 8.36 + +def test_invalid_addsemester(): + obj = Cgpa() + + # Test if the addsemester function is defined + with pytest.raises(TypeError): + obj.addsemester(-1) + + with pytest.raises(TypeError): + obj.addsemester(15) + + with pytest.raises(TypeError): + obj.addsemester('hello') + + with pytest.raises(TypeError): + obj.addsemester(3.14) + +def test_addsemester_with_no_subjects(): + obj = Cgpa() + + gpa_obj = Gpa() + + # Test if the addsemester function is defined + with pytest.raises(ValueError): + obj.addsemester(gpa_obj) + + assert len(obj.semester) == 0 + +def test_addsemester_with_fail_gradepoints(): + obj = Cgpa() + + gpa_obj = Gpa() + + # add subjects + credits = [2, 3, 3, 4, 2, 4, 4, 3] + grade_points = [1, 2, 4, 2, 1, 2, 4, 2] + + for i in range(len(credits)): + gpa_obj.addsubject(credits[i], grade_points[i]) + + # Test if the addsemester function is defined + with pytest.raises(ValueError): + obj.addsemester(gpa_obj) + + assert len(obj.semester) == 0 + +def test_addsemester_multiple_semesters(): + obj = Cgpa() + + gpa_obj1 = Gpa() + gpa_obj2 = Gpa() + gpa_obj3 = Gpa() + + # sem 1 + credits = [2, 3, 3, 4, 2, 4, 4, 3] + grade_points = [8, 8, 7, 8, 10, 9, 9, 8] + + for i in range(len(credits)): + gpa_obj1.addsubject(credits[i], grade_points[i]) + + # sem 2 + credits = [2, 4, 3, 4, 2, 2, 4, 2] + grade_points = [7, 8, 7, 8, 10, 6, 9, 8] + + for i in range(len(credits)): + gpa_obj2.addsubject(credits[i], grade_points[i]) + + # sem 3 + credits = [2, 3, 4, 3, 2, 3, 4, 3] + grade_points = [10, 8, 7, 8, 7, 9, 7, 8] + + for i in range(len(credits)): + gpa_obj3.addsubject(credits[i], grade_points[i]) + + # Test if the addsemester function is defined + obj.addsemester(gpa_obj1) + obj.addsemester(gpa_obj2) + obj.addsemester(gpa_obj3) + + assert len(obj.semester) == 3 + + assert obj.semester[0].gpa == 8.36 + assert obj.semester[1].gpa == 7.956521739130435 + assert obj.semester[2].gpa == 7.875 + + + +# calc function test cases +def test_cgpa_calc(): + obj = Cgpa() + + gpa_obj = Gpa() + + # add subjects + credits = [2, 3, 3, 4, 2, 4, 4, 3] + grade_points = [8, 8, 7, 8, 10, 9, 9, 8] + + for i in range(len(credits)): + gpa_obj.addsubject(credits[i], grade_points[i]) + + obj.addsemester(gpa_obj) + + # Test if the calc function is defined + obj.calc() + + assert obj.cgpa == 8.36 \ No newline at end of file diff --git a/tests/test_module.py b/tests/test_rollno.py similarity index 100% rename from tests/test_module.py rename to tests/test_rollno.py