From f4b2a1eadf83b7641eef3bc8643a19918ff87112 Mon Sep 17 00:00:00 2001 From: snowykte0426 Date: Mon, 13 Apr 2026 09:50:04 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=EC=8B=9C=EA=B0=84=ED=91=9C=20API?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EA=B8=89=EC=8B=9D/?= =?UTF-8?q?=ED=95=99=EC=82=AC=EC=9D=BC=EC=A0=95=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EB=9E=98=ED=8D=BC=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- src/datagsm_openapi/__init__.py | 4 +- src/datagsm_openapi/api/__init__.py | 3 +- src/datagsm_openapi/api/neis.py | 85 ++++++++++++++++++++++++-- src/datagsm_openapi/models/__init__.py | 6 +- src/datagsm_openapi/models/neis.py | 74 +++++++++++++++++++++- 6 files changed, 164 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d86eecb..6730aa5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "datagsm-openapi-sdk" -version = "1.1.1" +version = "1.2.0" description = "Python SDK for DataGSM OpenAPI" readme = "README.md" requires-python = ">=3.9" diff --git a/src/datagsm_openapi/__init__.py b/src/datagsm_openapi/__init__.py index 1993bf8..386a770 100644 --- a/src/datagsm_openapi/__init__.py +++ b/src/datagsm_openapi/__init__.py @@ -22,6 +22,7 @@ ScheduleRequest, StudentApi, StudentRequest, + TimetableRequest, ) from .client import DataGsmClient from .exceptions import ( @@ -36,7 +37,7 @@ ValidationException, ) -__version__ = "1.1.1" +__version__ = "1.2.0" __all__ = [ "BadRequestException", @@ -56,6 +57,7 @@ "ServerErrorException", "StudentApi", "StudentRequest", + "TimetableRequest", "UnauthorizedException", "ValidationException", ] diff --git a/src/datagsm_openapi/api/__init__.py b/src/datagsm_openapi/api/__init__.py index 77f0b6a..ef6bf92 100644 --- a/src/datagsm_openapi/api/__init__.py +++ b/src/datagsm_openapi/api/__init__.py @@ -1,7 +1,7 @@ """API modules for DataGSM OpenAPI SDK.""" from .club import ClubApi, ClubRequest -from .neis import MealRequest, NeisApi, ScheduleRequest +from .neis import MealRequest, NeisApi, ScheduleRequest, TimetableRequest from .project import ProjectApi, ProjectRequest from .student import StudentApi, StudentRequest @@ -15,4 +15,5 @@ "ScheduleRequest", "StudentApi", "StudentRequest", + "TimetableRequest", ] diff --git a/src/datagsm_openapi/api/neis.py b/src/datagsm_openapi/api/neis.py index cd63672..93ff366 100644 --- a/src/datagsm_openapi/api/neis.py +++ b/src/datagsm_openapi/api/neis.py @@ -4,7 +4,7 @@ from datetime import date as Date from typing import Optional -from ..models import Meal, Schedule +from ..models import Meal, MealResponse, Schedule, ScheduleResponse, Timetable, TimetableResponse from ._base import BaseApi @@ -68,10 +68,47 @@ def to_params(self) -> dict[str, Optional[object]]: return params +@dataclass +class TimetableRequest: + """시간표 조회 요청 파라미터 (Timetable Query Parameters). + + Use either 'date' for a single day or 'start_date' and 'end_date' for a date range. + Either 'date' or both 'start_date' and 'end_date' must be provided. + + Attributes: + grade: Grade to query (1-3) + class_num: Class number to query (1-4) + date: Single date to query + start_date: Start date for range query + end_date: End date for range query + """ + + grade: int = 0 + class_num: int = 0 + date: Optional[Date] = None + start_date: Optional[Date] = None + end_date: Optional[Date] = None + + def to_params(self) -> dict[str, Optional[object]]: + """Convert to query parameters dictionary. + + Returns: + Dictionary of query parameters + """ + params: dict[str, Optional[object]] = { + "grade": self.grade, + "classNum": self.class_num, + "date": self.date, + "startDate": self.start_date, + "endDate": self.end_date, + } + return params + + class NeisApi(BaseApi): """NEIS 데이터 API (NEIS Data API). - Provides methods for querying school meal and schedule information from NEIS. + Provides methods for querying school meal, schedule, and timetable information from NEIS. """ def get_meals(self, request: Optional[MealRequest] = None) -> list[Meal]: @@ -104,7 +141,10 @@ def get_meals(self, request: Optional[MealRequest] = None) -> list[Meal]: >>> week_meals = api.get_meals(request) """ req = request or MealRequest(date=Date.today()) - return self._get("/v1/neis/meals", params=req.to_params(), response_type=list[Meal]) + response = self._get( + "/v1/neis/meals", params=req.to_params(), response_type=MealResponse + ) + return response.meals def get_schedules(self, request: Optional[ScheduleRequest] = None) -> list[Schedule]: """학사일정 정보 조회 (Get Schedule Information). @@ -136,6 +176,41 @@ def get_schedules(self, request: Optional[ScheduleRequest] = None) -> list[Sched >>> month_schedules = api.get_schedules(request) """ req = request or ScheduleRequest(date=Date.today()) - return self._get( - "/v1/neis/schedules", params=req.to_params(), response_type=list[Schedule] + response = self._get( + "/v1/neis/schedules", params=req.to_params(), response_type=ScheduleResponse + ) + return response.schedules + + def get_timetables(self, request: TimetableRequest) -> list[Timetable]: + """시간표 정보 조회 (Get Timetable Information). + + Query school timetable information for a specific grade, class, and date or date range. + Either 'date' or both 'start_date' and 'end_date' must be provided in the request. + + Args: + request: Query parameters (grade, class_num, and date/date range are required) + + Returns: + List of timetables + + Example: + >>> from datetime import date + >>> api = NeisApi(http_client) + >>> + >>> # Get timetable for a specific date + >>> request = TimetableRequest(grade=1, class_num=1, date=date(2026, 4, 7)) + >>> timetables = api.get_timetables(request) + >>> + >>> # Get timetables for a date range + >>> request = TimetableRequest( + ... grade=2, + ... class_num=3, + ... start_date=date(2026, 4, 7), + ... end_date=date(2026, 4, 11) + ... ) + >>> week_timetables = api.get_timetables(request) + """ + response = self._get( + "/v1/neis/timetables", params=request.to_params(), response_type=TimetableResponse ) + return response.timetables diff --git a/src/datagsm_openapi/models/__init__.py b/src/datagsm_openapi/models/__init__.py index bb2ba5e..bc1654e 100644 --- a/src/datagsm_openapi/models/__init__.py +++ b/src/datagsm_openapi/models/__init__.py @@ -14,7 +14,7 @@ StudentRole, StudentSortBy, ) -from .neis import Meal, Schedule +from .neis import Meal, MealResponse, Schedule, ScheduleResponse, Timetable, TimetableResponse from .project import ParticipantInfo, Project, ProjectResponse from .student import Student, StudentResponse @@ -28,13 +28,17 @@ "CommonApiResponse", "Major", "Meal", + "MealResponse", "MealType", "ParticipantInfo", "Project", "ProjectResponse", "ProjectSortBy", "Schedule", + "ScheduleResponse", "Sex", + "Timetable", + "TimetableResponse", "SortDirection", "Student", "StudentResponse", diff --git a/src/datagsm_openapi/models/neis.py b/src/datagsm_openapi/models/neis.py index ed610de..e967231 100644 --- a/src/datagsm_openapi/models/neis.py +++ b/src/datagsm_openapi/models/neis.py @@ -1,4 +1,4 @@ -"""NEIS-related models (급식, 학사일정) for DataGSM OpenAPI SDK.""" +"""NEIS-related models (급식, 학사일정, 시간표) for DataGSM OpenAPI SDK.""" from datetime import date from typing import Optional @@ -56,6 +56,18 @@ class Meal(BaseModel): model_config = ConfigDict(populate_by_name=True) +class MealResponse(BaseModel): + """급식 목록 응답 래퍼 (Meal List Response Wrapper). + + Attributes: + meals: List of meal information + """ + + meals: list[Meal] = Field(default_factory=list, description="List of meals") + + model_config = ConfigDict(populate_by_name=True) + + class Schedule(BaseModel): """학사일정 정보 (School Schedule Information). @@ -100,3 +112,63 @@ class Schedule(BaseModel): ) model_config = ConfigDict(populate_by_name=True) + + +class ScheduleResponse(BaseModel): + """학사일정 목록 응답 래퍼 (Schedule List Response Wrapper). + + Attributes: + schedules: List of schedule information + """ + + schedules: list[Schedule] = Field(default_factory=list, description="List of schedules") + + model_config = ConfigDict(populate_by_name=True) + + +class Timetable(BaseModel): + """시간표 정보 (Timetable Information). + + Information about school timetables from NEIS. + + Attributes: + timetable_id: Timetable ID + school_code: School code + school_name: School name + office_code: Education office code + office_name: Education office name + timetable_date: Timetable date + academic_year: Academic year + semester: Semester (nullable) + grade: Grade (1-3) + class_num: Class number (1-4) + period: Period number + subject: Subject name (nullable) + """ + + timetable_id: str = Field(..., alias="timetableId", description="Timetable ID") + school_code: str = Field(..., alias="schoolCode", description="School code") + school_name: str = Field(..., alias="schoolName", description="School name") + office_code: str = Field(..., alias="officeCode", description="Education office code") + office_name: str = Field(..., alias="officeName", description="Education office name") + timetable_date: date = Field(..., alias="timetableDate", description="Timetable date") + academic_year: str = Field(..., alias="academicYear", description="Academic year") + semester: Optional[str] = Field(None, alias="semester", description="Semester") + grade: int = Field(..., alias="grade", description="Grade (1-3)") + class_num: int = Field(..., alias="classNum", description="Class number (1-4)") + period: int = Field(..., alias="period", description="Period number") + subject: Optional[str] = Field(None, alias="subject", description="Subject name") + + model_config = ConfigDict(populate_by_name=True) + + +class TimetableResponse(BaseModel): + """시간표 목록 응답 래퍼 (Timetable List Response Wrapper). + + Attributes: + timetables: List of timetable information + """ + + timetables: list[Timetable] = Field(default_factory=list, description="List of timetables") + + model_config = ConfigDict(populate_by_name=True) From d6cecb88e187538c9a05d0526c2846f471ebd73a Mon Sep 17 00:00:00 2001 From: snowykte0426 Date: Mon, 13 Apr 2026 09:53:47 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20`=5F=5Fall=5F=5F`=20=EC=95=8C?= =?UTF-8?q?=ED=8C=8C=EB=B2=B3=20=EC=88=9C=EC=84=9C=20=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/datagsm_openapi/models/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/datagsm_openapi/models/__init__.py b/src/datagsm_openapi/models/__init__.py index bc1654e..51b2d77 100644 --- a/src/datagsm_openapi/models/__init__.py +++ b/src/datagsm_openapi/models/__init__.py @@ -37,10 +37,10 @@ "Schedule", "ScheduleResponse", "Sex", - "Timetable", - "TimetableResponse", "SortDirection", "Student", + "Timetable", + "TimetableResponse", "StudentResponse", "StudentRole", "StudentSortBy", From 308c4184ccfc0f092143874390c9ca8b71c0c1ab Mon Sep 17 00:00:00 2001 From: snowykte0426 Date: Mon, 13 Apr 2026 09:59:41 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20`=5F=5Finit=5F=5F.py`=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=95=8C=ED=8C=8C=EB=B2=B3=20=EC=88=9C=EC=84=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 -- src/datagsm_openapi/models/__init__.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6730aa5..7c61eb4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,8 +93,6 @@ select = [ "RUF", # Ruff-specific rules ] ignore = [ - "ANN101", # Missing type annotation for self - "ANN102", # Missing type annotation for cls "ANN401", # Dynamically typed expressions (Any) - needed for *args in context managers "N818", # Exception naming - DataGsmException is the intended name "UP035", # typing.Type vs type - Python 3.9 compatibility diff --git a/src/datagsm_openapi/models/__init__.py b/src/datagsm_openapi/models/__init__.py index 51b2d77..886d471 100644 --- a/src/datagsm_openapi/models/__init__.py +++ b/src/datagsm_openapi/models/__init__.py @@ -39,9 +39,9 @@ "Sex", "SortDirection", "Student", - "Timetable", - "TimetableResponse", "StudentResponse", "StudentRole", "StudentSortBy", + "Timetable", + "TimetableResponse", ]