From baafe61201d8583c917597f536a6debb84b3d10c Mon Sep 17 00:00:00 2001 From: pooriya Date: Thu, 24 Jul 2025 00:39:46 +0330 Subject: [PATCH 01/12] the new structure of project be set --- syncortexGA/{constraints.py => constraints/__init__.py} | 0 .../{genetic_algorithm.py => constraints/hard_constraints.py} | 0 syncortexGA/{models.py => constraints/soft_constraints.py} | 0 syncortexGA/genetic/__init__.py | 0 syncortexGA/genetic/algorithm.py | 0 syncortexGA/models/__init__.py | 0 syncortexGA/models/timetable_model.py | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename syncortexGA/{constraints.py => constraints/__init__.py} (100%) rename syncortexGA/{genetic_algorithm.py => constraints/hard_constraints.py} (100%) rename syncortexGA/{models.py => constraints/soft_constraints.py} (100%) create mode 100644 syncortexGA/genetic/__init__.py create mode 100644 syncortexGA/genetic/algorithm.py create mode 100644 syncortexGA/models/__init__.py create mode 100644 syncortexGA/models/timetable_model.py diff --git a/syncortexGA/constraints.py b/syncortexGA/constraints/__init__.py similarity index 100% rename from syncortexGA/constraints.py rename to syncortexGA/constraints/__init__.py diff --git a/syncortexGA/genetic_algorithm.py b/syncortexGA/constraints/hard_constraints.py similarity index 100% rename from syncortexGA/genetic_algorithm.py rename to syncortexGA/constraints/hard_constraints.py diff --git a/syncortexGA/models.py b/syncortexGA/constraints/soft_constraints.py similarity index 100% rename from syncortexGA/models.py rename to syncortexGA/constraints/soft_constraints.py diff --git a/syncortexGA/genetic/__init__.py b/syncortexGA/genetic/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/syncortexGA/genetic/algorithm.py b/syncortexGA/genetic/algorithm.py new file mode 100644 index 0000000..e69de29 diff --git a/syncortexGA/models/__init__.py b/syncortexGA/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/syncortexGA/models/timetable_model.py b/syncortexGA/models/timetable_model.py new file mode 100644 index 0000000..e69de29 From 9d2774fb431d3bcaaa946f6f185c6f743ea73873 Mon Sep 17 00:00:00 2001 From: pooriya Date: Thu, 24 Jul 2025 05:22:25 +0330 Subject: [PATCH 02/12] the models and there relations be designed and implemented --- syncortexGA/models/timetable_model.py | 165 ++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/syncortexGA/models/timetable_model.py b/syncortexGA/models/timetable_model.py index e69de29..bb409c2 100644 --- a/syncortexGA/models/timetable_model.py +++ b/syncortexGA/models/timetable_model.py @@ -0,0 +1,165 @@ +from pydantic import BaseModel, model_validator +from typing import List, Optional, Literal + +# ========== TimeSlot ========== +class TimeSlot(BaseModel): + """ + Represents a specific time slot on a given day. + """ + day: Literal["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday"] + start: str # Format: "HH:MM", e.g., "08:00" + end: str # Format: "HH:MM", e.g., "10:00" + +# ========== Session Patterns ========== + +class FixedSessionPattern(BaseModel): + """ + Represents a fixed pattern with exactly 2 weekly sessions. + """ + slots: List[TimeSlot] + + @model_validator(mode='after') + def check_two_slots(cls, model): + if len(model.slots) != 2: + raise ValueError("FixedSessionPattern must have exactly 2 slots") + return model + + +class AlternatingSessionPattern(BaseModel): + """ + Represents a pattern where a course alternates between two slots weekly. + Paired with another course (by course_id), and occurs on even or odd weeks. + """ + fixed_slot: TimeSlot + alternating_slot: TimeSlot + alternating_mode: Literal["odd", "even"] + paired_course_id: int + + +class SessionPattern(BaseModel): + """ + Wraps either a fixed or alternating session pattern. + Only one of the patterns must be set based on the type. + """ + type: Literal["fixed", "alternating"] + fixed_pattern: Optional[FixedSessionPattern] = None + alternating_pattern: Optional[AlternatingSessionPattern] = None + + @model_validator(mode='after') + def validate_only_one_pattern(cls, model): + pattern_type = model.type + fixed = model.fixed_pattern + alternating = model.alternating_pattern + + if pattern_type == "fixed": + if not fixed or alternating: + raise ValueError("For type='fixed', only fixed_pattern must be set") + elif pattern_type == "alternating": + if not alternating or fixed: + raise ValueError("For type='alternating', only alternating_pattern must be set") + else: + raise ValueError("Invalid session pattern type") + + return model + +# ========== Course ========== + +class Course(BaseModel): + """ + Represents a course that may or may not include a lab. + Lab courses must define a lab slot and cannot have a session pattern. + Non-lab courses must define a session pattern and cannot have a lab slot. + """ + id: int + name: str + code: str + instructor_id: int + + has_lab: bool = False + lab_slot: Optional[TimeSlot] = None + lab_room_id: Optional[int] = None + lab_instructor_id: Optional[int] = None + + session_pattern: Optional[SessionPattern] = None + + @model_validator(mode='after') + def validate_course(cls, model): + has_lab = model.has_lab + lab_slot = model.lab_slot + session_pattern = model.session_pattern + + if has_lab: + if not lab_slot: + raise ValueError("Lab slot must be defined for lab courses") + if session_pattern: + raise ValueError("Lab courses must not have session pattern") + else: + if not session_pattern: + raise ValueError("Non-lab courses must have a session pattern") + if lab_slot: + raise ValueError("Non-lab courses must not have lab slot") + + return model + +# ========== Student ========== + +class Student(BaseModel): + """ + Represents a student belonging to a specific group. + """ + full_name: str + student_number: str # e.g., "401234567" + group_id: int # Refers to StudentGroup.id + +# ========== StudentGroup ========== + +class StudentGroup(BaseModel): + """ + Represents a group of students (e.g., a class or cohort). + """ + id: int + name: str # e.g., "CS-401" + major: str # e.g., "Computer Engineering" + entry_year: int # e.g., 2022 + +# ========== Instructor ========== + +class Instructor(BaseModel): + """ + Represents an instructor with available and preferred time slots. + """ + id: int + full_name: str + available_slots: List[TimeSlot] # Instructor is only assignable within these slots + preferred_slots: Optional[List[TimeSlot]] = None # Soft preference (optional) + +# ========== Room ========== + +class Room(BaseModel): + """ + Represents a classroom or lab with capacity constraints. + """ + id: int + name: str # e.g., "Room 101" + capacity: int # Max number of students + is_lab: bool = False # True if lab; used only for lab courses + +# ========== Scheduled Session & Timetable ========== + +class ScheduledSession(BaseModel): + """ + Represents a scheduled class session (theory or lab). + """ + course_id: int # ID of the course being taught + group_id: int # ID of the student group attending the session + instructor_id: int # ID of the instructor assigned to this session + room_id: int # ID of the room assigned for this session + slot: TimeSlot # Scheduled time slot for the session + type: Literal["theory", "lab"] # Type of session (theory or lab) + +class Timetable(BaseModel): + """ + Represents the final schedule as a list of sessions. + This is the output of the scheduling algorithm. + """ + sessions: List[ScheduledSession] From 4cd35ef5c2e663668a572c4707e9e2645fa75434 Mon Sep 17 00:00:00 2001 From: pooriya Date: Thu, 24 Jul 2025 05:24:49 +0330 Subject: [PATCH 03/12] The model test was set and successfully finished --- tests/unit/test_models.py | 255 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 tests/unit/test_models.py diff --git a/tests/unit/test_models.py b/tests/unit/test_models.py new file mode 100644 index 0000000..0f9299a --- /dev/null +++ b/tests/unit/test_models.py @@ -0,0 +1,255 @@ +import pytest +from pydantic import ValidationError +from syncortexGA.models.timetable_model import ( + TimeSlot, FixedSessionPattern, AlternatingSessionPattern, + SessionPattern, Course, Student, StudentGroup, + Instructor, Room, ScheduledSession, Timetable +) + +# ====== TimeSlot Tests ====== + +def test_timeslot_valid(): + ts = TimeSlot(day="Monday", start="08:00", end="10:00") + assert ts.day == "Monday" + assert ts.start == "08:00" + assert ts.end == "10:00" + +def test_timeslot_invalid_day(): + with pytest.raises(ValidationError): + TimeSlot(day="Friday", start="08:00", end="10:00") # Friday not allowed + +def test_timeslot_invalid_time_format(): + # Pydantic does not auto-check format, but you could add regex validation if needed. + ts = TimeSlot(day="Monday", start="8am", end="10am") # This passes but logically invalid + + +# ====== FixedSessionPattern Tests ====== + +def test_fixed_session_pattern_valid(): + slots = [ + TimeSlot(day="Saturday", start="08:00", end="10:00"), + TimeSlot(day="Monday", start="10:00", end="12:00") + ] + pattern = FixedSessionPattern(slots=slots) + assert len(pattern.slots) == 2 + +def test_fixed_session_pattern_invalid_count(): + slots = [TimeSlot(day="Saturday", start="08:00", end="10:00")] + with pytest.raises(ValidationError): + FixedSessionPattern(slots=slots) + +def test_fixed_session_pattern_empty(): + with pytest.raises(ValidationError): + FixedSessionPattern(slots=[]) + + +# ====== AlternatingSessionPattern Tests ====== + +def test_alternating_session_pattern_valid(): + fixed_slot = TimeSlot(day="Sunday", start="08:00", end="10:00") + alternating_slot = TimeSlot(day="Tuesday", start="10:00", end="12:00") + pattern = AlternatingSessionPattern( + fixed_slot=fixed_slot, + alternating_slot=alternating_slot, + alternating_mode="odd", + paired_course_id=123 + ) + assert pattern.alternating_mode == "odd" + +def test_alternating_session_pattern_invalid_mode(): + fixed_slot = TimeSlot(day="Sunday", start="08:00", end="10:00") + alternating_slot = TimeSlot(day="Tuesday", start="10:00", end="12:00") + with pytest.raises(ValidationError): + AlternatingSessionPattern( + fixed_slot=fixed_slot, + alternating_slot=alternating_slot, + alternating_mode="week3", # invalid mode + paired_course_id=123 + ) + +# ====== SessionPattern Tests ====== + +def test_session_pattern_fixed_valid(): + slots = [ + TimeSlot(day="Saturday", start="08:00", end="10:00"), + TimeSlot(day="Monday", start="10:00", end="12:00") + ] + fixed = FixedSessionPattern(slots=slots) + sp = SessionPattern(type="fixed", fixed_pattern=fixed) + assert sp.type == "fixed" + +def test_session_pattern_alternating_valid(): + fixed_slot = TimeSlot(day="Sunday", start="08:00", end="10:00") + alternating_slot = TimeSlot(day="Tuesday", start="10:00", end="12:00") + alt = AlternatingSessionPattern( + fixed_slot=fixed_slot, + alternating_slot=alternating_slot, + alternating_mode="even", + paired_course_id=99 + ) + sp = SessionPattern(type="alternating", alternating_pattern=alt) + assert sp.type == "alternating" + +def test_session_pattern_invalid_type(): + with pytest.raises(ValidationError): + SessionPattern(type="random") + +def test_session_pattern_mismatch_fixed(): + slots = [ + TimeSlot(day="Saturday", start="08:00", end="10:00"), + TimeSlot(day="Monday", start="10:00", end="12:00") + ] + fixed = FixedSessionPattern(slots=slots) + # alternating_pattern also set: invalid for type fixed + alt = AlternatingSessionPattern( + fixed_slot=slots[0], + alternating_slot=slots[1], + alternating_mode="odd", + paired_course_id=5 + ) + with pytest.raises(ValidationError): + SessionPattern(type="fixed", fixed_pattern=fixed, alternating_pattern=alt) + +def test_session_pattern_mismatch_alternating(): + slots = [ + TimeSlot(day="Saturday", start="08:00", end="10:00"), + TimeSlot(day="Monday", start="10:00", end="12:00") + ] + fixed = FixedSessionPattern(slots=slots) + with pytest.raises(ValidationError): + SessionPattern(type="alternating", fixed_pattern=fixed) + + +# ====== Course Tests ====== + +def test_course_lab_valid(): + lab_slot = TimeSlot(day="Wednesday", start="14:00", end="16:00") + course = Course( + id=1, name="Physics Lab", code="PHY101", instructor_id=10, + has_lab=True, lab_slot=lab_slot, lab_room_id=3, lab_instructor_id=15 + ) + assert course.has_lab is True + assert course.lab_slot == lab_slot + assert course.session_pattern is None + +def test_course_lab_missing_lab_slot(): + with pytest.raises(ValidationError): + Course( + id=2, name="Chemistry Lab", code="CHEM101", instructor_id=11, + has_lab=True + ) + +def test_course_lab_with_session_pattern(): + lab_slot = TimeSlot(day="Wednesday", start="14:00", end="16:00") + fixed_pattern = FixedSessionPattern(slots=[ + TimeSlot(day="Monday", start="08:00", end="10:00"), + TimeSlot(day="Thursday", start="08:00", end="10:00") + ]) + session_pattern = SessionPattern(type="fixed", fixed_pattern=fixed_pattern) + with pytest.raises(ValidationError): + Course( + id=3, name="Bio Lab", code="BIO101", instructor_id=12, + has_lab=True, lab_slot=lab_slot, session_pattern=session_pattern + ) + +def test_course_non_lab_valid(): + fixed_pattern = FixedSessionPattern(slots=[ + TimeSlot(day="Monday", start="08:00", end="10:00"), + TimeSlot(day="Thursday", start="08:00", end="10:00") + ]) + session_pattern = SessionPattern(type="fixed", fixed_pattern=fixed_pattern) + course = Course( + id=4, name="Math", code="MATH101", instructor_id=13, + has_lab=False, session_pattern=session_pattern + ) + assert course.has_lab is False + assert course.session_pattern == session_pattern + assert course.lab_slot is None + +def test_course_non_lab_missing_session_pattern(): + with pytest.raises(ValidationError): + Course( + id=5, name="History", code="HIST101", instructor_id=14, + has_lab=False + ) + +def test_course_non_lab_with_lab_slot(): + lab_slot = TimeSlot(day="Wednesday", start="14:00", end="16:00") + with pytest.raises(ValidationError): + Course( + id=6, name="English", code="ENG101", instructor_id=15, + has_lab=False, lab_slot=lab_slot + ) + +# ====== Student Tests ====== + +def test_student_valid(): + student = Student(full_name="Alice Smith", student_number="401234567", group_id=100) + assert student.student_number == "401234567" + +def test_student_missing_field(): + with pytest.raises(ValidationError): + Student(full_name="Bob") + +# ====== StudentGroup Tests ====== + +def test_studentgroup_valid(): + group = StudentGroup(id=1, name="CS-401", major="Computer Engineering", entry_year=2023) + assert group.name == "CS-401" + +# ====== Instructor Tests ====== + +def test_instructor_valid(): + available = [TimeSlot(day="Monday", start="08:00", end="12:00")] + preferred = [TimeSlot(day="Monday", start="10:00", end="12:00")] + instructor = Instructor(id=1, full_name="Dr. John", available_slots=available, preferred_slots=preferred) + assert instructor.full_name == "Dr. John" + +def test_instructor_no_preferred(): + available = [TimeSlot(day="Tuesday", start="08:00", end="10:00")] + instructor = Instructor(id=2, full_name="Dr. Jane", available_slots=available) + assert instructor.preferred_slots is None + +# ====== Room Tests ====== + +def test_room_valid(): + room = Room(id=1, name="Room 101", capacity=30, is_lab=False) + assert room.capacity == 30 + +def test_room_lab_flag(): + room = Room(id=2, name="Lab 1", capacity=20, is_lab=True) + assert room.is_lab is True + +# ====== ScheduledSession Tests ====== + +def test_scheduled_session_valid(): + slot = TimeSlot(day="Thursday", start="10:00", end="12:00") + session = ScheduledSession( + course_id=1, group_id=10, instructor_id=5, + room_id=3, slot=slot, type="theory" + ) + assert session.type == "theory" + +def test_scheduled_session_invalid_type(): + slot = TimeSlot(day="Thursday", start="10:00", end="12:00") + with pytest.raises(ValidationError): + ScheduledSession( + course_id=1, group_id=10, instructor_id=5, + room_id=3, slot=slot, type="exam" + ) + +# ====== Timetable Tests ====== + +def test_timetable_valid(): + slot = TimeSlot(day="Thursday", start="10:00", end="12:00") + session = ScheduledSession( + course_id=1, group_id=10, instructor_id=5, + room_id=3, slot=slot, type="lab" + ) + timetable = Timetable(sessions=[session]) + assert len(timetable.sessions) == 1 + +def test_timetable_empty(): + timetable = Timetable(sessions=[]) + assert timetable.sessions == [] + From 4847e65a16745310174528942db9a4e5007c9f3a Mon Sep 17 00:00:00 2001 From: pooriya adib rad Date: Thu, 24 Jul 2025 05:27:07 +0330 Subject: [PATCH 04/12] Update main.yml --- .github/workflows/main.yml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1bd8001..5bcf545 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,19 +30,19 @@ jobs: pip install -r requirements.txt pip install -r requirements-dev.txt -# - name: Lint with flake8 -# run: flake8 . + - name: Lint with flake8 + run: flake8 . -# - name: Format check with black -# run: black --check . + - name: Format check with black + run: black --check . -# - name: Type check with mypy -# run: mypy . + - name: Type check with mypy + run: mypy . -# - name: Run tests with coverage -# run: pytest --cov=./ --cov-report=xml --cov-report=term + - name: Run tests with coverage + run: pytest --cov=./ --cov-report=xml --cov-report=term - # - name: Upload coverage to Codecov - # uses: codecov/codecov-action@v3 - # with: - # files: ./coverage.xml + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + files: ./coverage.xml From 46d3964a94a1a50b5e7e16418ed0981e33ff7db2 Mon Sep 17 00:00:00 2001 From: pooriya Date: Thu, 24 Jul 2025 05:30:35 +0330 Subject: [PATCH 05/12] The ci workflow was updated --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5bcf545..eefb576 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [main, master] + branches: [ main, master,develop ] pull_request: - branches: [main, master] + branches: [ main, master ] jobs: test: From 35bb036d8a15dc893002d9202b5a645e75fe0082 Mon Sep 17 00:00:00 2001 From: pooriya Date: Thu, 24 Jul 2025 05:38:47 +0330 Subject: [PATCH 06/12] added new structuer of data for using in models --- syncortexGA/models/timetable_model.py | 40 +++++++++++++++++---------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/syncortexGA/models/timetable_model.py b/syncortexGA/models/timetable_model.py index bb409c2..4b7ae10 100644 --- a/syncortexGA/models/timetable_model.py +++ b/syncortexGA/models/timetable_model.py @@ -1,14 +1,19 @@ -from pydantic import BaseModel, model_validator +from pydantic import BaseModel, model_validator, constr from typing import List, Optional, Literal +# Accepts only time strings in 24-hour format like "08:00", "23:59" +TimeStr = constr(pattern=r"^(?:[01]\d|2[0-3]):[0-5]\d$") + + # ========== TimeSlot ========== class TimeSlot(BaseModel): """ Represents a specific time slot on a given day. """ day: Literal["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday"] - start: str # Format: "HH:MM", e.g., "08:00" - end: str # Format: "HH:MM", e.g., "10:00" + start: TimeStr # Format: "HH:MM", e.g., "08:00" + end: TimeStr # Format: "HH:MM", e.g., "10:00" + # ========== Session Patterns ========== @@ -62,6 +67,7 @@ def validate_only_one_pattern(cls, model): return model + # ========== Course ========== class Course(BaseModel): @@ -101,6 +107,7 @@ def validate_course(cls, model): return model + # ========== Student ========== class Student(BaseModel): @@ -109,7 +116,8 @@ class Student(BaseModel): """ full_name: str student_number: str # e.g., "401234567" - group_id: int # Refers to StudentGroup.id + group_id: int # Refers to StudentGroup.id + # ========== StudentGroup ========== @@ -118,10 +126,11 @@ class StudentGroup(BaseModel): Represents a group of students (e.g., a class or cohort). """ id: int - name: str # e.g., "CS-401" - major: str # e.g., "Computer Engineering" + name: str # e.g., "CS-401" + major: str # e.g., "Computer Engineering" entry_year: int # e.g., 2022 + # ========== Instructor ========== class Instructor(BaseModel): @@ -130,9 +139,10 @@ class Instructor(BaseModel): """ id: int full_name: str - available_slots: List[TimeSlot] # Instructor is only assignable within these slots + available_slots: List[TimeSlot] # Instructor is only assignable within these slots preferred_slots: Optional[List[TimeSlot]] = None # Soft preference (optional) + # ========== Room ========== class Room(BaseModel): @@ -140,23 +150,25 @@ class Room(BaseModel): Represents a classroom or lab with capacity constraints. """ id: int - name: str # e.g., "Room 101" - capacity: int # Max number of students + name: str # e.g., "Room 101" + capacity: int # Max number of students is_lab: bool = False # True if lab; used only for lab courses + # ========== Scheduled Session & Timetable ========== class ScheduledSession(BaseModel): """ Represents a scheduled class session (theory or lab). """ - course_id: int # ID of the course being taught - group_id: int # ID of the student group attending the session - instructor_id: int # ID of the instructor assigned to this session - room_id: int # ID of the room assigned for this session - slot: TimeSlot # Scheduled time slot for the session + course_id: int # ID of the course being taught + group_id: int # ID of the student group attending the session + instructor_id: int # ID of the instructor assigned to this session + room_id: int # ID of the room assigned for this session + slot: TimeSlot # Scheduled time slot for the session type: Literal["theory", "lab"] # Type of session (theory or lab) + class Timetable(BaseModel): """ Represents the final schedule as a list of sessions. From c19da2d183296796688fa3ee0d1dc2ec5b745b92 Mon Sep 17 00:00:00 2001 From: pooriya Date: Thu, 24 Jul 2025 05:39:10 +0330 Subject: [PATCH 07/12] the test models format file be updated --- tests/unit/test_models.py | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/tests/unit/test_models.py b/tests/unit/test_models.py index 0f9299a..e1d785e 100644 --- a/tests/unit/test_models.py +++ b/tests/unit/test_models.py @@ -6,6 +6,7 @@ Instructor, Room, ScheduledSession, Timetable ) + # ====== TimeSlot Tests ====== def test_timeslot_valid(): @@ -14,13 +15,16 @@ def test_timeslot_valid(): assert ts.start == "08:00" assert ts.end == "10:00" + def test_timeslot_invalid_day(): with pytest.raises(ValidationError): TimeSlot(day="Friday", start="08:00", end="10:00") # Friday not allowed + def test_timeslot_invalid_time_format(): - # Pydantic does not auto-check format, but you could add regex validation if needed. - ts = TimeSlot(day="Monday", start="8am", end="10am") # This passes but logically invalid + # Expecting failure because "8am" and "10am" are not valid "HH:MM" format + with pytest.raises(ValidationError): + TimeSlot(day="Monday", start="8am", end="10am") # ====== FixedSessionPattern Tests ====== @@ -33,11 +37,13 @@ def test_fixed_session_pattern_valid(): pattern = FixedSessionPattern(slots=slots) assert len(pattern.slots) == 2 + def test_fixed_session_pattern_invalid_count(): slots = [TimeSlot(day="Saturday", start="08:00", end="10:00")] with pytest.raises(ValidationError): FixedSessionPattern(slots=slots) + def test_fixed_session_pattern_empty(): with pytest.raises(ValidationError): FixedSessionPattern(slots=[]) @@ -56,6 +62,7 @@ def test_alternating_session_pattern_valid(): ) assert pattern.alternating_mode == "odd" + def test_alternating_session_pattern_invalid_mode(): fixed_slot = TimeSlot(day="Sunday", start="08:00", end="10:00") alternating_slot = TimeSlot(day="Tuesday", start="10:00", end="12:00") @@ -67,6 +74,7 @@ def test_alternating_session_pattern_invalid_mode(): paired_course_id=123 ) + # ====== SessionPattern Tests ====== def test_session_pattern_fixed_valid(): @@ -78,6 +86,7 @@ def test_session_pattern_fixed_valid(): sp = SessionPattern(type="fixed", fixed_pattern=fixed) assert sp.type == "fixed" + def test_session_pattern_alternating_valid(): fixed_slot = TimeSlot(day="Sunday", start="08:00", end="10:00") alternating_slot = TimeSlot(day="Tuesday", start="10:00", end="12:00") @@ -90,10 +99,12 @@ def test_session_pattern_alternating_valid(): sp = SessionPattern(type="alternating", alternating_pattern=alt) assert sp.type == "alternating" + def test_session_pattern_invalid_type(): with pytest.raises(ValidationError): SessionPattern(type="random") + def test_session_pattern_mismatch_fixed(): slots = [ TimeSlot(day="Saturday", start="08:00", end="10:00"), @@ -110,6 +121,7 @@ def test_session_pattern_mismatch_fixed(): with pytest.raises(ValidationError): SessionPattern(type="fixed", fixed_pattern=fixed, alternating_pattern=alt) + def test_session_pattern_mismatch_alternating(): slots = [ TimeSlot(day="Saturday", start="08:00", end="10:00"), @@ -132,6 +144,7 @@ def test_course_lab_valid(): assert course.lab_slot == lab_slot assert course.session_pattern is None + def test_course_lab_missing_lab_slot(): with pytest.raises(ValidationError): Course( @@ -139,6 +152,7 @@ def test_course_lab_missing_lab_slot(): has_lab=True ) + def test_course_lab_with_session_pattern(): lab_slot = TimeSlot(day="Wednesday", start="14:00", end="16:00") fixed_pattern = FixedSessionPattern(slots=[ @@ -152,6 +166,7 @@ def test_course_lab_with_session_pattern(): has_lab=True, lab_slot=lab_slot, session_pattern=session_pattern ) + def test_course_non_lab_valid(): fixed_pattern = FixedSessionPattern(slots=[ TimeSlot(day="Monday", start="08:00", end="10:00"), @@ -166,6 +181,7 @@ def test_course_non_lab_valid(): assert course.session_pattern == session_pattern assert course.lab_slot is None + def test_course_non_lab_missing_session_pattern(): with pytest.raises(ValidationError): Course( @@ -173,6 +189,7 @@ def test_course_non_lab_missing_session_pattern(): has_lab=False ) + def test_course_non_lab_with_lab_slot(): lab_slot = TimeSlot(day="Wednesday", start="14:00", end="16:00") with pytest.raises(ValidationError): @@ -181,22 +198,26 @@ def test_course_non_lab_with_lab_slot(): has_lab=False, lab_slot=lab_slot ) + # ====== Student Tests ====== def test_student_valid(): student = Student(full_name="Alice Smith", student_number="401234567", group_id=100) assert student.student_number == "401234567" + def test_student_missing_field(): with pytest.raises(ValidationError): Student(full_name="Bob") + # ====== StudentGroup Tests ====== -def test_studentgroup_valid(): +def test_student_group_valid(): group = StudentGroup(id=1, name="CS-401", major="Computer Engineering", entry_year=2023) assert group.name == "CS-401" + # ====== Instructor Tests ====== def test_instructor_valid(): @@ -205,21 +226,25 @@ def test_instructor_valid(): instructor = Instructor(id=1, full_name="Dr. John", available_slots=available, preferred_slots=preferred) assert instructor.full_name == "Dr. John" + def test_instructor_no_preferred(): available = [TimeSlot(day="Tuesday", start="08:00", end="10:00")] instructor = Instructor(id=2, full_name="Dr. Jane", available_slots=available) assert instructor.preferred_slots is None + # ====== Room Tests ====== def test_room_valid(): room = Room(id=1, name="Room 101", capacity=30, is_lab=False) assert room.capacity == 30 + def test_room_lab_flag(): room = Room(id=2, name="Lab 1", capacity=20, is_lab=True) assert room.is_lab is True + # ====== ScheduledSession Tests ====== def test_scheduled_session_valid(): @@ -230,6 +255,7 @@ def test_scheduled_session_valid(): ) assert session.type == "theory" + def test_scheduled_session_invalid_type(): slot = TimeSlot(day="Thursday", start="10:00", end="12:00") with pytest.raises(ValidationError): @@ -238,6 +264,7 @@ def test_scheduled_session_invalid_type(): room_id=3, slot=slot, type="exam" ) + # ====== Timetable Tests ====== def test_timetable_valid(): @@ -249,7 +276,7 @@ def test_timetable_valid(): timetable = Timetable(sessions=[session]) assert len(timetable.sessions) == 1 + def test_timetable_empty(): timetable = Timetable(sessions=[]) assert timetable.sessions == [] - From 41c7be8091a1b1986894c1b14c81e127628457e7 Mon Sep 17 00:00:00 2001 From: pooriya Date: Thu, 24 Jul 2025 06:08:53 +0330 Subject: [PATCH 08/12] the format of code be regular --- syncortexGA/exceptions.py | 1 - syncortexGA/models/timetable_model.py | 119 ++++++-------- tests/unit/test_models.py | 225 ++++++++++++++++---------- 3 files changed, 184 insertions(+), 161 deletions(-) diff --git a/syncortexGA/exceptions.py b/syncortexGA/exceptions.py index 991aa1a..e69de29 100644 --- a/syncortexGA/exceptions.py +++ b/syncortexGA/exceptions.py @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/syncortexGA/models/timetable_model.py b/syncortexGA/models/timetable_model.py index 4b7ae10..8da0a80 100644 --- a/syncortexGA/models/timetable_model.py +++ b/syncortexGA/models/timetable_model.py @@ -7,23 +7,20 @@ # ========== TimeSlot ========== class TimeSlot(BaseModel): - """ - Represents a specific time slot on a given day. - """ + """Represents a specific time slot on a given day.""" + day: Literal["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday"] - start: TimeStr # Format: "HH:MM", e.g., "08:00" - end: TimeStr # Format: "HH:MM", e.g., "10:00" + start: TimeStr # Format: "HH:MM" + end: TimeStr # Format: "HH:MM" # ========== Session Patterns ========== - class FixedSessionPattern(BaseModel): - """ - Represents a fixed pattern with exactly 2 weekly sessions. - """ + """Represents a fixed pattern with exactly 2 weekly sessions.""" + slots: List[TimeSlot] - @model_validator(mode='after') + @model_validator(mode="after") def check_two_slots(cls, model): if len(model.slots) != 2: raise ValueError("FixedSessionPattern must have exactly 2 slots") @@ -33,8 +30,9 @@ def check_two_slots(cls, model): class AlternatingSessionPattern(BaseModel): """ Represents a pattern where a course alternates between two slots weekly. - Paired with another course (by course_id), and occurs on even or odd weeks. + Paired with another course and occurs on even or odd weeks. """ + fixed_slot: TimeSlot alternating_slot: TimeSlot alternating_mode: Literal["odd", "even"] @@ -46,85 +44,70 @@ class SessionPattern(BaseModel): Wraps either a fixed or alternating session pattern. Only one of the patterns must be set based on the type. """ + type: Literal["fixed", "alternating"] fixed_pattern: Optional[FixedSessionPattern] = None alternating_pattern: Optional[AlternatingSessionPattern] = None - @model_validator(mode='after') + @model_validator(mode="after") def validate_only_one_pattern(cls, model): - pattern_type = model.type - fixed = model.fixed_pattern - alternating = model.alternating_pattern - - if pattern_type == "fixed": - if not fixed or alternating: + if model.type == "fixed": + if not model.fixed_pattern or model.alternating_pattern: raise ValueError("For type='fixed', only fixed_pattern must be set") - elif pattern_type == "alternating": - if not alternating or fixed: - raise ValueError("For type='alternating', only alternating_pattern must be set") - else: - raise ValueError("Invalid session pattern type") - + elif model.type == "alternating": + if not model.alternating_pattern or model.fixed_pattern: + raise ValueError( + "For type='alternating', " "only alternating_pattern must be set" + ) return model # ========== Course ========== - class Course(BaseModel): """ Represents a course that may or may not include a lab. Lab courses must define a lab slot and cannot have a session pattern. Non-lab courses must define a session pattern and cannot have a lab slot. """ + id: int name: str code: str instructor_id: int - has_lab: bool = False lab_slot: Optional[TimeSlot] = None lab_room_id: Optional[int] = None lab_instructor_id: Optional[int] = None - session_pattern: Optional[SessionPattern] = None - @model_validator(mode='after') + @model_validator(mode="after") def validate_course(cls, model): - has_lab = model.has_lab - lab_slot = model.lab_slot - session_pattern = model.session_pattern - - if has_lab: - if not lab_slot: + if model.has_lab: + if not model.lab_slot: raise ValueError("Lab slot must be defined for lab courses") - if session_pattern: + if model.session_pattern: raise ValueError("Lab courses must not have session pattern") else: - if not session_pattern: + if not model.session_pattern: raise ValueError("Non-lab courses must have a session pattern") - if lab_slot: + if model.lab_slot: raise ValueError("Non-lab courses must not have lab slot") - return model # ========== Student ========== - class Student(BaseModel): - """ - Represents a student belonging to a specific group. - """ + """Represents a student belonging to a specific group.""" + full_name: str student_number: str # e.g., "401234567" group_id: int # Refers to StudentGroup.id # ========== StudentGroup ========== - class StudentGroup(BaseModel): - """ - Represents a group of students (e.g., a class or cohort). - """ + """Represents a group of students (e.g., a class or cohort).""" + id: int name: str # e.g., "CS-401" major: str # e.g., "Computer Engineering" @@ -132,46 +115,38 @@ class StudentGroup(BaseModel): # ========== Instructor ========== - class Instructor(BaseModel): - """ - Represents an instructor with available and preferred time slots. - """ + """Represents an instructor with available and preferred time slots.""" + id: int full_name: str - available_slots: List[TimeSlot] # Instructor is only assignable within these slots - preferred_slots: Optional[List[TimeSlot]] = None # Soft preference (optional) + available_slots: List[TimeSlot] + preferred_slots: Optional[List[TimeSlot]] = None # ========== Room ========== - class Room(BaseModel): - """ - Represents a classroom or lab with capacity constraints. - """ + """Represents a classroom or lab with capacity constraints.""" + id: int name: str # e.g., "Room 101" - capacity: int # Max number of students - is_lab: bool = False # True if lab; used only for lab courses + capacity: int + is_lab: bool = False # ========== Scheduled Session & Timetable ========== - class ScheduledSession(BaseModel): - """ - Represents a scheduled class session (theory or lab). - """ - course_id: int # ID of the course being taught - group_id: int # ID of the student group attending the session - instructor_id: int # ID of the instructor assigned to this session - room_id: int # ID of the room assigned for this session - slot: TimeSlot # Scheduled time slot for the session - type: Literal["theory", "lab"] # Type of session (theory or lab) + """Represents a scheduled class session (theory or lab).""" + + course_id: int + group_id: int + instructor_id: int + room_id: int + slot: TimeSlot + type: Literal["theory", "lab"] class Timetable(BaseModel): - """ - Represents the final schedule as a list of sessions. - This is the output of the scheduling algorithm. - """ + """Represents the final schedule as a list of sessions.""" + sessions: List[ScheduledSession] diff --git a/tests/unit/test_models.py b/tests/unit/test_models.py index e1d785e..a3f577a 100644 --- a/tests/unit/test_models.py +++ b/tests/unit/test_models.py @@ -1,14 +1,23 @@ import pytest from pydantic import ValidationError from syncortexGA.models.timetable_model import ( - TimeSlot, FixedSessionPattern, AlternatingSessionPattern, - SessionPattern, Course, Student, StudentGroup, - Instructor, Room, ScheduledSession, Timetable + TimeSlot, + FixedSessionPattern, + AlternatingSessionPattern, + SessionPattern, + Course, + Student, + StudentGroup, + Instructor, + Room, + ScheduledSession, + Timetable, ) # ====== TimeSlot Tests ====== + def test_timeslot_valid(): ts = TimeSlot(day="Monday", start="08:00", end="10:00") assert ts.day == "Monday" @@ -18,30 +27,31 @@ def test_timeslot_valid(): def test_timeslot_invalid_day(): with pytest.raises(ValidationError): - TimeSlot(day="Friday", start="08:00", end="10:00") # Friday not allowed + TimeSlot(day="Friday", start="08:00", end="10:00") def test_timeslot_invalid_time_format(): - # Expecting failure because "8am" and "10am" are not valid "HH:MM" format with pytest.raises(ValidationError): TimeSlot(day="Monday", start="8am", end="10am") # ====== FixedSessionPattern Tests ====== + def test_fixed_session_pattern_valid(): slots = [ TimeSlot(day="Saturday", start="08:00", end="10:00"), - TimeSlot(day="Monday", start="10:00", end="12:00") + TimeSlot(day="Monday", start="10:00", end="12:00"), ] pattern = FixedSessionPattern(slots=slots) assert len(pattern.slots) == 2 def test_fixed_session_pattern_invalid_count(): - slots = [TimeSlot(day="Saturday", start="08:00", end="10:00")] with pytest.raises(ValidationError): - FixedSessionPattern(slots=slots) + FixedSessionPattern( + slots=[TimeSlot(day="Saturday", start="08:00", end="10:00")] + ) def test_fixed_session_pattern_empty(): @@ -51,50 +61,47 @@ def test_fixed_session_pattern_empty(): # ====== AlternatingSessionPattern Tests ====== + def test_alternating_session_pattern_valid(): - fixed_slot = TimeSlot(day="Sunday", start="08:00", end="10:00") - alternating_slot = TimeSlot(day="Tuesday", start="10:00", end="12:00") pattern = AlternatingSessionPattern( - fixed_slot=fixed_slot, - alternating_slot=alternating_slot, + fixed_slot=TimeSlot(day="Sunday", start="08:00", end="10:00"), + alternating_slot=TimeSlot(day="Tuesday", start="10:00", end="12:00"), alternating_mode="odd", - paired_course_id=123 + paired_course_id=123, ) assert pattern.alternating_mode == "odd" def test_alternating_session_pattern_invalid_mode(): - fixed_slot = TimeSlot(day="Sunday", start="08:00", end="10:00") - alternating_slot = TimeSlot(day="Tuesday", start="10:00", end="12:00") with pytest.raises(ValidationError): AlternatingSessionPattern( - fixed_slot=fixed_slot, - alternating_slot=alternating_slot, - alternating_mode="week3", # invalid mode - paired_course_id=123 + fixed_slot=TimeSlot(day="Sunday", start="08:00", end="10:00"), + alternating_slot=TimeSlot(day="Tuesday", start="10:00", end="12:00"), + alternating_mode="week3", + paired_course_id=123, ) # ====== SessionPattern Tests ====== + def test_session_pattern_fixed_valid(): - slots = [ - TimeSlot(day="Saturday", start="08:00", end="10:00"), - TimeSlot(day="Monday", start="10:00", end="12:00") - ] - fixed = FixedSessionPattern(slots=slots) + fixed = FixedSessionPattern( + slots=[ + TimeSlot(day="Saturday", start="08:00", end="10:00"), + TimeSlot(day="Monday", start="10:00", end="12:00"), + ] + ) sp = SessionPattern(type="fixed", fixed_pattern=fixed) assert sp.type == "fixed" def test_session_pattern_alternating_valid(): - fixed_slot = TimeSlot(day="Sunday", start="08:00", end="10:00") - alternating_slot = TimeSlot(day="Tuesday", start="10:00", end="12:00") alt = AlternatingSessionPattern( - fixed_slot=fixed_slot, - alternating_slot=alternating_slot, + fixed_slot=TimeSlot(day="Sunday", start="08:00", end="10:00"), + alternating_slot=TimeSlot(day="Tuesday", start="10:00", end="12:00"), alternating_mode="even", - paired_course_id=99 + paired_course_id=99, ) sp = SessionPattern(type="alternating", alternating_pattern=alt) assert sp.type == "alternating" @@ -106,101 +113,121 @@ def test_session_pattern_invalid_type(): def test_session_pattern_mismatch_fixed(): - slots = [ - TimeSlot(day="Saturday", start="08:00", end="10:00"), - TimeSlot(day="Monday", start="10:00", end="12:00") - ] - fixed = FixedSessionPattern(slots=slots) - # alternating_pattern also set: invalid for type fixed + fixed = FixedSessionPattern( + slots=[ + TimeSlot(day="Saturday", start="08:00", end="10:00"), + TimeSlot(day="Monday", start="10:00", end="12:00"), + ] + ) alt = AlternatingSessionPattern( - fixed_slot=slots[0], - alternating_slot=slots[1], + fixed_slot=fixed.slots[0], + alternating_slot=fixed.slots[1], alternating_mode="odd", - paired_course_id=5 + paired_course_id=5, ) with pytest.raises(ValidationError): SessionPattern(type="fixed", fixed_pattern=fixed, alternating_pattern=alt) def test_session_pattern_mismatch_alternating(): - slots = [ - TimeSlot(day="Saturday", start="08:00", end="10:00"), - TimeSlot(day="Monday", start="10:00", end="12:00") - ] - fixed = FixedSessionPattern(slots=slots) + fixed = FixedSessionPattern( + slots=[ + TimeSlot(day="Saturday", start="08:00", end="10:00"), + TimeSlot(day="Monday", start="10:00", end="12:00"), + ] + ) with pytest.raises(ValidationError): SessionPattern(type="alternating", fixed_pattern=fixed) # ====== Course Tests ====== + def test_course_lab_valid(): - lab_slot = TimeSlot(day="Wednesday", start="14:00", end="16:00") course = Course( - id=1, name="Physics Lab", code="PHY101", instructor_id=10, - has_lab=True, lab_slot=lab_slot, lab_room_id=3, lab_instructor_id=15 + id=1, + name="Physics Lab", + code="PHY101", + instructor_id=10, + has_lab=True, + lab_slot=TimeSlot(day="Wednesday", start="14:00", end="16:00"), + lab_room_id=3, + lab_instructor_id=15, ) assert course.has_lab is True - assert course.lab_slot == lab_slot - assert course.session_pattern is None def test_course_lab_missing_lab_slot(): with pytest.raises(ValidationError): Course( - id=2, name="Chemistry Lab", code="CHEM101", instructor_id=11, - has_lab=True + id=2, name="Chemistry Lab", code="CHEM101", instructor_id=11, has_lab=True ) def test_course_lab_with_session_pattern(): lab_slot = TimeSlot(day="Wednesday", start="14:00", end="16:00") - fixed_pattern = FixedSessionPattern(slots=[ - TimeSlot(day="Monday", start="08:00", end="10:00"), - TimeSlot(day="Thursday", start="08:00", end="10:00") - ]) - session_pattern = SessionPattern(type="fixed", fixed_pattern=fixed_pattern) + session_pattern = SessionPattern( + type="fixed", + fixed_pattern=FixedSessionPattern( + slots=[ + TimeSlot(day="Monday", start="08:00", end="10:00"), + TimeSlot(day="Thursday", start="08:00", end="10:00"), + ] + ), + ) with pytest.raises(ValidationError): Course( - id=3, name="Bio Lab", code="BIO101", instructor_id=12, - has_lab=True, lab_slot=lab_slot, session_pattern=session_pattern + id=3, + name="Bio Lab", + code="BIO101", + instructor_id=12, + has_lab=True, + lab_slot=lab_slot, + session_pattern=session_pattern, ) def test_course_non_lab_valid(): - fixed_pattern = FixedSessionPattern(slots=[ - TimeSlot(day="Monday", start="08:00", end="10:00"), - TimeSlot(day="Thursday", start="08:00", end="10:00") - ]) - session_pattern = SessionPattern(type="fixed", fixed_pattern=fixed_pattern) + session_pattern = SessionPattern( + type="fixed", + fixed_pattern=FixedSessionPattern( + slots=[ + TimeSlot(day="Monday", start="08:00", end="10:00"), + TimeSlot(day="Thursday", start="08:00", end="10:00"), + ] + ), + ) course = Course( - id=4, name="Math", code="MATH101", instructor_id=13, - has_lab=False, session_pattern=session_pattern + id=4, + name="Math", + code="MATH101", + instructor_id=13, + has_lab=False, + session_pattern=session_pattern, ) assert course.has_lab is False - assert course.session_pattern == session_pattern - assert course.lab_slot is None def test_course_non_lab_missing_session_pattern(): with pytest.raises(ValidationError): - Course( - id=5, name="History", code="HIST101", instructor_id=14, - has_lab=False - ) + Course(id=5, name="History", code="HIST101", instructor_id=14, has_lab=False) def test_course_non_lab_with_lab_slot(): - lab_slot = TimeSlot(day="Wednesday", start="14:00", end="16:00") with pytest.raises(ValidationError): Course( - id=6, name="English", code="ENG101", instructor_id=15, - has_lab=False, lab_slot=lab_slot + id=6, + name="English", + code="ENG101", + instructor_id=15, + has_lab=False, + lab_slot=TimeSlot(day="Wednesday", start="14:00", end="16:00"), ) # ====== Student Tests ====== + def test_student_valid(): student = Student(full_name="Alice Smith", student_number="401234567", group_id=100) assert student.student_number == "401234567" @@ -213,28 +240,39 @@ def test_student_missing_field(): # ====== StudentGroup Tests ====== + def test_student_group_valid(): - group = StudentGroup(id=1, name="CS-401", major="Computer Engineering", entry_year=2023) + group = StudentGroup( + id=1, name="CS-401", major="Computer Engineering", entry_year=2023 + ) assert group.name == "CS-401" # ====== Instructor Tests ====== + def test_instructor_valid(): - available = [TimeSlot(day="Monday", start="08:00", end="12:00")] - preferred = [TimeSlot(day="Monday", start="10:00", end="12:00")] - instructor = Instructor(id=1, full_name="Dr. John", available_slots=available, preferred_slots=preferred) + instructor = Instructor( + id=1, + full_name="Dr. John", + available_slots=[TimeSlot(day="Monday", start="08:00", end="12:00")], + preferred_slots=[TimeSlot(day="Monday", start="10:00", end="12:00")], + ) assert instructor.full_name == "Dr. John" def test_instructor_no_preferred(): - available = [TimeSlot(day="Tuesday", start="08:00", end="10:00")] - instructor = Instructor(id=2, full_name="Dr. Jane", available_slots=available) + instructor = Instructor( + id=2, + full_name="Dr. Jane", + available_slots=[TimeSlot(day="Tuesday", start="08:00", end="10:00")], + ) assert instructor.preferred_slots is None # ====== Room Tests ====== + def test_room_valid(): room = Room(id=1, name="Room 101", capacity=30, is_lab=False) assert room.capacity == 30 @@ -247,31 +285,42 @@ def test_room_lab_flag(): # ====== ScheduledSession Tests ====== + def test_scheduled_session_valid(): - slot = TimeSlot(day="Thursday", start="10:00", end="12:00") session = ScheduledSession( - course_id=1, group_id=10, instructor_id=5, - room_id=3, slot=slot, type="theory" + course_id=1, + group_id=10, + instructor_id=5, + room_id=3, + slot=TimeSlot(day="Thursday", start="10:00", end="12:00"), + type="theory", ) assert session.type == "theory" def test_scheduled_session_invalid_type(): - slot = TimeSlot(day="Thursday", start="10:00", end="12:00") with pytest.raises(ValidationError): ScheduledSession( - course_id=1, group_id=10, instructor_id=5, - room_id=3, slot=slot, type="exam" + course_id=1, + group_id=10, + instructor_id=5, + room_id=3, + slot=TimeSlot(day="Thursday", start="10:00", end="12:00"), + type="exam", ) # ====== Timetable Tests ====== + def test_timetable_valid(): - slot = TimeSlot(day="Thursday", start="10:00", end="12:00") session = ScheduledSession( - course_id=1, group_id=10, instructor_id=5, - room_id=3, slot=slot, type="lab" + course_id=1, + group_id=10, + instructor_id=5, + room_id=3, + slot=TimeSlot(day="Thursday", start="10:00", end="12:00"), + type="lab", ) timetable = Timetable(sessions=[session]) assert len(timetable.sessions) == 1 From a0626fc5570240016e8176d3c8ba344d2c33a861 Mon Sep 17 00:00:00 2001 From: pooriya Date: Thu, 24 Jul 2025 06:18:04 +0330 Subject: [PATCH 09/12] make flake8 config file --- .flake8 | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .flake8 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..08abe94 --- /dev/null +++ b/.flake8 @@ -0,0 +1,11 @@ +[flake8] +max-line-length = 88 +extend-ignore = E203, W503, F403, F401 +exclude = + .git, + __pycache__, + build, + dist, + .venv, + env, + venv \ No newline at end of file From a14c79a379aca9272b391ec1e5fa1711ef2a776f Mon Sep 17 00:00:00 2001 From: pooriya Date: Thu, 24 Jul 2025 06:23:02 +0330 Subject: [PATCH 10/12] the dev required package be updated --- requirements-dev.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 8fc8616..2c2d885 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,4 +6,5 @@ coverage # Code quality flake8 black -mypy \ No newline at end of file +mypy +pydantic[mypy]==2.11.7 \ No newline at end of file From e919f1af32ee2ffacb123c3fd1df7b35cbbf7ecc Mon Sep 17 00:00:00 2001 From: pooriya Date: Thu, 24 Jul 2025 06:23:22 +0330 Subject: [PATCH 11/12] the mypy issue be fixed --- syncortexGA/models/timetable_model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/syncortexGA/models/timetable_model.py b/syncortexGA/models/timetable_model.py index 8da0a80..aa5b391 100644 --- a/syncortexGA/models/timetable_model.py +++ b/syncortexGA/models/timetable_model.py @@ -1,8 +1,8 @@ -from pydantic import BaseModel, model_validator, constr -from typing import List, Optional, Literal +from pydantic import BaseModel, model_validator, StringConstraints +from typing import List, Optional, Literal, Annotated # Accepts only time strings in 24-hour format like "08:00", "23:59" -TimeStr = constr(pattern=r"^(?:[01]\d|2[0-3]):[0-5]\d$") +TimeStr = Annotated[str, StringConstraints(pattern=r"^(?:[01]\d|2[0-3]):[0-5]\d$")] # ========== TimeSlot ========== From 41f7760cf3b06c59fa7c1542d83ec92cdd6eea0f Mon Sep 17 00:00:00 2001 From: pooriya Date: Thu, 24 Jul 2025 06:23:53 +0330 Subject: [PATCH 12/12] the workflow be updated --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index eefb576..d17db44 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [ main, master,develop ] + branches: [ main ] pull_request: - branches: [ main, master ] + branches: [ main ] jobs: test: