Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(courses): 新增學期選擇功能 #59

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48,786 changes: 23,511 additions & 25,275 deletions data/courses/11120.json

Large diffs are not rendered by default.

62,540 changes: 62,540 additions & 0 deletions data/courses/11220.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/api/constant/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from . import buses, general
from . import buses, courses, general
3 changes: 3 additions & 0 deletions src/api/constant/courses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from src.api import schemas

DEFAULT_SEMESTER = schemas.courses.CourseSemester("11210")
25 changes: 21 additions & 4 deletions src/api/models/courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import operator
import re

from src.api import constant, schemas
from src.utils import cached_request


Expand Down Expand Up @@ -179,8 +180,12 @@ class Processor:
"https://www.ccxp.nthu.edu.tw/ccxp/INQUIRE/JH/OPENDATA/open_course_data.json"
)

def __init__(self, json_path=None) -> None:
self.course_data = self._get_course_data(json_path)
def __init__(
self,
semester: schemas.courses.CourseSemester = constant.courses.DEFAULT_SEMESTER,
) -> None:
self.semester = semester.value
self.course_data = self._get_course_data(f"data/courses/{self.semester}.json")

def _get_course_data(self, json_path=None) -> list[CoursesData]:
"""TODO: error handler."""
Expand All @@ -194,8 +199,20 @@ def _get_course_data(self, json_path=None) -> list[CoursesData]:
course_data_dict_list = json.loads(course_data_resp)
return list(map(CoursesData, course_data_dict_list))

def update(self, json_path=None):
self.course_data = self._get_course_data(json_path)
def set_semester(
self,
semester: schemas.courses.CourseSemester = constant.courses.DEFAULT_SEMESTER,
):
if semester.value == self.semester:
return

self.semester = semester.value
if self.semester == "latest":
self.course_data = self._get_course_data()
else:
self.course_data = self._get_course_data(
f"data/courses/{self.semester}.json"
)

def list_selected_fields(self, field) -> list:
"""列出所有課程的某個欄位。
Expand Down
26 changes: 25 additions & 1 deletion src/api/routers/courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@
from src.api.models.courses import Conditions, Processor

router = APIRouter()
courses = Processor(json_path="data/courses/11210.json")
courses = Processor(semester=constant.courses.DEFAULT_SEMESTER)


@router.get("/", response_model=list[schemas.courses.CourseData])
async def get_all_courses_list(
response: Response,
semester: schemas.courses.CourseSemester = Query(
constant.courses.DEFAULT_SEMESTER, example="11210", description="學期代碼"
),
limits: int = constant.general.LIMITS_QUERY,
):
"""
取得所有課程。
"""
courses.set_semester(semester)
result = courses.course_data[:limits]
response.headers["X-Total-Count"] = str(len(result))
return result
Expand Down Expand Up @@ -53,11 +57,15 @@ async def get_selected_fields_list(
field_name: schemas.courses.CourseFieldName = Path(
..., example="id", description="欄位名稱"
),
semester: schemas.courses.CourseSemester = Query(
constant.courses.DEFAULT_SEMESTER, example="11210", description="學期代碼"
),
limits: int = constant.general.LIMITS_QUERY,
):
"""
取得指定欄位的列表。
"""
courses.set_semester(semester)
result = courses.list_selected_fields(field_name)[:limits]
return result

Expand All @@ -70,11 +78,15 @@ async def get_selected_field_and_value_data(
..., example="chinese_title", description="搜尋的欄位名稱"
),
value: str = Path(..., example="產業創新與生涯探索", description="搜尋的值"),
semester: schemas.courses.CourseSemester = Query(
constant.courses.DEFAULT_SEMESTER, example="11210", description="學期代碼"
),
limits: int = constant.general.LIMITS_QUERY,
):
"""
取得指定欄位滿足搜尋值的課程列表。
"""
courses.set_semester(semester)
condition = Conditions(field_name, value, False)
result = courses.query(condition)[:limits]
return result
Expand All @@ -84,11 +96,15 @@ async def get_selected_field_and_value_data(
async def get_courses_list(
list_name: schemas.courses.CourseListName,
response: Response,
semester: schemas.courses.CourseSemester = Query(
constant.courses.DEFAULT_SEMESTER, example="11210", description="學期代碼"
),
limits: int = constant.general.LIMITS_QUERY,
) -> list[schemas.courses.CourseData]:
"""
取得指定類型的課程列表。
"""
courses.set_semester(semester)
if list_name == "16weeks":
condition = Conditions("note", "16週課程", True)
elif list_name == "microcredits":
Expand All @@ -112,11 +128,15 @@ async def search_by_field_and_value(
value: str = Query(
..., example="產業.+生涯", description="搜尋的值(可以使用 Regex,正則表達式)"
),
semester: schemas.courses.CourseSemester = Query(
constant.courses.DEFAULT_SEMESTER, example="11210", description="學期代碼"
),
limits: int = constant.general.LIMITS_QUERY,
):
"""
取得指定欄位滿足搜尋值的課程列表。
"""
courses.set_semester(semester)
condition = Conditions(field, value, True)
result = courses.query(condition)[:limits]
return result
Expand Down Expand Up @@ -185,11 +205,15 @@ async def get_courses_by_condition(
},
}
),
semester: schemas.courses.CourseSemester = Query(
constant.courses.DEFAULT_SEMESTER, example="11210", description="學期代碼"
),
limits: int = constant.general.LIMITS_QUERY,
):
"""
根據條件取得課程。
"""
courses.set_semester(semester)
if type(query_condition) is schemas.courses.CourseCondition:
condition = Conditions(
query_condition.row_field.value,
Expand Down
7 changes: 7 additions & 0 deletions src/api/schemas/courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,10 @@ class CourseListName(str, Enum):
weeks16 = "16weeks"
microcredits = "microcredits"
xclass = "xclass"


class CourseSemester(str, Enum):
latest = "latest"
_11120 = "11120"
_11210 = "11210"
_11220 = "11220"
41 changes: 30 additions & 11 deletions tests/test_courses.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,45 +45,64 @@
@pytest.mark.parametrize(
"url, status_code",
[
("/courses/", 200),
("/courses/fields/info", 200),
],
)
def test_courses_info_endpoints(url, status_code):
response = client.get(url=url)
assert response.status_code == status_code


@pytest.mark.parametrize("semester", [_.value for _ in schemas.courses.CourseSemester])
@pytest.mark.parametrize(
"url, status_code",
[
("/courses/", 200),
("/courses/lists/16weeks", 200),
("/courses/lists/microcredits", 200),
("/courses/lists/xclass", 200),
],
)
def test_courses_endpoints(url, status_code):
response = client.get(url=url)
def test_courses_endpoints(semester, url, status_code):
response = client.get(url=url + f"?semester={semester}")
assert response.status_code == status_code


@pytest.mark.parametrize("semester", [_.value for _ in schemas.courses.CourseSemester])
@pytest.mark.parametrize(
"field_name", [_.value for _ in schemas.courses.CourseFieldName]
)
def test_courses_fields(field_name):
response = client.get(url=f"/courses/fields/{field_name}")
def test_courses_fields(semester, field_name):
response = client.get(url=f"/courses/fields/{field_name}/?semester={semester}")
assert response.status_code == 200


@pytest.mark.parametrize("semester", [_.value for _ in schemas.courses.CourseSemester])
@pytest.mark.parametrize(
"field_name", [_.value for _ in schemas.courses.CourseFieldName]
)
@pytest.mark.parametrize("value", ["testing"])
def test_courses_fields_with_values(field_name, value):
response = client.get(url=f"/courses/fields/{field_name}/{value}")
def test_courses_fields_with_values(semester, field_name, value):
response = client.get(
url=f"/courses/fields/{field_name}/{value}/?semester={semester}"
)
assert response.status_code == 200


@pytest.mark.parametrize("semester", [_.value for _ in schemas.courses.CourseSemester])
@pytest.mark.parametrize(
"field_name", [_.value for _ in schemas.courses.CourseFieldName]
)
@pytest.mark.parametrize("value", ["testing"])
def test_courses_search(field_name, value):
response = client.get(url=f"/courses/searches?field={field_name}&value={value}")
def test_courses_search(semester, field_name, value):
response = client.get(
url=f"/courses/searches?field={field_name}&value={value}&semester={semester}"
)
assert response.status_code == 200


@pytest.mark.parametrize("semester", [_.value for _ in schemas.courses.CourseSemester])
@pytest.mark.parametrize("body", [one_condition, two_conditions, multiple_conditions])
def test_courses_search_post(body):
response = client.post(url="/courses/searches", json=body)
def test_courses_search_post(semester, body):
response = client.post(url=f"/courses/searches?semester={semester}", json=body)
assert response.status_code == 200