datagsm-openapi-sdk 1.0.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,141 @@
1
+ """NEIS API module for DataGSM OpenAPI SDK."""
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import date as Date
5
+ from typing import Optional
6
+
7
+ from ..models import Meal, Schedule
8
+ from ._base import BaseApi
9
+
10
+
11
+ @dataclass
12
+ class MealRequest:
13
+ """급식 조회 요청 파라미터 (Meal Query Parameters).
14
+
15
+ Use either 'date' for a single day or 'from_date' and 'to_date' for a date range.
16
+
17
+ Attributes:
18
+ date: Single date to query
19
+ from_date: Start date for range query
20
+ to_date: End date for range query
21
+ """
22
+
23
+ date: Optional[Date] = None
24
+ from_date: Optional[Date] = None
25
+ to_date: Optional[Date] = None
26
+
27
+ def to_params(self) -> dict[str, Optional[object]]:
28
+ """Convert to query parameters dictionary.
29
+
30
+ Returns:
31
+ Dictionary of query parameters
32
+ """
33
+ params: dict[str, Optional[object]] = {
34
+ "date": self.date,
35
+ "fromDate": self.from_date,
36
+ "toDate": self.to_date,
37
+ }
38
+ return params
39
+
40
+
41
+ @dataclass
42
+ class ScheduleRequest:
43
+ """학사일정 조회 요청 파라미터 (Schedule Query Parameters).
44
+
45
+ Use either 'date' for a single day or 'from_date' and 'to_date' for a date range.
46
+
47
+ Attributes:
48
+ date: Single date to query
49
+ from_date: Start date for range query
50
+ to_date: End date for range query
51
+ """
52
+
53
+ date: Optional[Date] = None
54
+ from_date: Optional[Date] = None
55
+ to_date: Optional[Date] = None
56
+
57
+ def to_params(self) -> dict[str, Optional[object]]:
58
+ """Convert to query parameters dictionary.
59
+
60
+ Returns:
61
+ Dictionary of query parameters
62
+ """
63
+ params: dict[str, Optional[object]] = {
64
+ "date": self.date,
65
+ "fromDate": self.from_date,
66
+ "toDate": self.to_date,
67
+ }
68
+ return params
69
+
70
+
71
+ class NeisApi(BaseApi):
72
+ """NEIS 데이터 API (NEIS Data API).
73
+
74
+ Provides methods for querying school meal and schedule information from NEIS.
75
+ """
76
+
77
+ def get_meals(self, request: Optional[MealRequest] = None) -> list[Meal]:
78
+ """급식 정보 조회 (Get Meal Information).
79
+
80
+ Query school meal information for a specific date or date range.
81
+
82
+ Args:
83
+ request: Query parameters (optional, defaults to today)
84
+
85
+ Returns:
86
+ List of meals
87
+
88
+ Example:
89
+ >>> from datetime import date
90
+ >>> api = NeisApi(http_client)
91
+ >>>
92
+ >>> # Get today's meals
93
+ >>> today_meals = api.get_meals()
94
+ >>>
95
+ >>> # Get meals for a specific date
96
+ >>> request = MealRequest(date=date(2026, 2, 3))
97
+ >>> meals = api.get_meals(request)
98
+ >>>
99
+ >>> # Get meals for a date range
100
+ >>> request = MealRequest(
101
+ ... from_date=date(2026, 2, 1),
102
+ ... to_date=date(2026, 2, 7)
103
+ ... )
104
+ >>> week_meals = api.get_meals(request)
105
+ """
106
+ req = request or MealRequest(date=Date.today())
107
+ return self._get("/v1/neis/meals", params=req.to_params(), response_type=list[Meal])
108
+
109
+ def get_schedules(self, request: Optional[ScheduleRequest] = None) -> list[Schedule]:
110
+ """학사일정 정보 조회 (Get Schedule Information).
111
+
112
+ Query school schedule/event information for a specific date or date range.
113
+
114
+ Args:
115
+ request: Query parameters (optional, defaults to today)
116
+
117
+ Returns:
118
+ List of schedules
119
+
120
+ Example:
121
+ >>> from datetime import date
122
+ >>> api = NeisApi(http_client)
123
+ >>>
124
+ >>> # Get today's schedules
125
+ >>> today_events = api.get_schedules()
126
+ >>>
127
+ >>> # Get schedules for a specific date
128
+ >>> request = ScheduleRequest(date=date(2026, 3, 1))
129
+ >>> schedules = api.get_schedules(request)
130
+ >>>
131
+ >>> # Get schedules for a date range
132
+ >>> request = ScheduleRequest(
133
+ ... from_date=date(2026, 3, 1),
134
+ ... to_date=date(2026, 3, 31)
135
+ ... )
136
+ >>> month_schedules = api.get_schedules(request)
137
+ """
138
+ req = request or ScheduleRequest(date=Date.today())
139
+ return self._get(
140
+ "/v1/neis/schedules", params=req.to_params(), response_type=list[Schedule]
141
+ )
@@ -0,0 +1,103 @@
1
+ """Project API module for DataGSM OpenAPI SDK."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Optional
5
+
6
+ from ..models import Project, ProjectResponse, ProjectSortBy, SortDirection
7
+ from ._base import BaseApi
8
+
9
+
10
+ @dataclass
11
+ class ProjectRequest:
12
+ """프로젝트 조회 요청 파라미터 (Project Query Parameters).
13
+
14
+ Attributes:
15
+ project_id: Project ID for exact match
16
+ project_name: Project name for filtering
17
+ club_id: Club ID filter
18
+ page: Page number (default: 0)
19
+ size: Page size (default: 100)
20
+ sort_by: Sort field
21
+ sort_direction: Sort direction (default: ASC)
22
+ """
23
+
24
+ project_id: Optional[int] = None
25
+ project_name: Optional[str] = None
26
+ club_id: Optional[int] = None
27
+ page: int = 0
28
+ size: int = 100
29
+ sort_by: Optional[ProjectSortBy] = None
30
+ sort_direction: SortDirection = SortDirection.ASC
31
+
32
+ def to_params(self) -> dict[str, Optional[object]]:
33
+ """Convert to query parameters dictionary.
34
+
35
+ Returns:
36
+ Dictionary of query parameters
37
+ """
38
+ params: dict[str, Optional[object]] = {
39
+ "projectId": self.project_id,
40
+ "projectName": self.project_name,
41
+ "clubId": self.club_id,
42
+ "page": self.page,
43
+ "size": self.size,
44
+ "sortBy": self.sort_by.value if self.sort_by else None,
45
+ "sortDirection": self.sort_direction.value,
46
+ }
47
+ return params
48
+
49
+
50
+ class ProjectApi(BaseApi):
51
+ """프로젝트 데이터 API (Project Data API).
52
+
53
+ Provides methods for querying project information.
54
+ """
55
+
56
+ def get_projects(self, request: Optional[ProjectRequest] = None) -> ProjectResponse:
57
+ """프로젝트 목록 조회 (Get Project List).
58
+
59
+ Query projects with optional filtering, sorting, and pagination.
60
+
61
+ Args:
62
+ request: Query parameters (optional)
63
+
64
+ Returns:
65
+ Paginated project response
66
+
67
+ Example:
68
+ >>> api = ProjectApi(http_client)
69
+ >>> # Get all projects
70
+ >>> response = api.get_projects()
71
+ >>> print(f"Total: {response.total_elements}")
72
+ >>>
73
+ >>> # Filter by club
74
+ >>> request = ProjectRequest(club_id=1)
75
+ >>> club_projects = api.get_projects(request)
76
+ """
77
+ req = request or ProjectRequest()
78
+ return self._get("/v1/projects", params=req.to_params(), response_type=ProjectResponse)
79
+
80
+ def get_project(self, project_id: int) -> Optional[Project]:
81
+ """특정 프로젝트 조회 (Get Specific Project).
82
+
83
+ Retrieve a single project by ID.
84
+
85
+ Args:
86
+ project_id: Project ID
87
+
88
+ Returns:
89
+ Project information if found, None otherwise
90
+
91
+ Example:
92
+ >>> api = ProjectApi(http_client)
93
+ >>> project = api.get_project(1)
94
+ >>> if project:
95
+ ... print(f"Project: {project.name}")
96
+ ... print(f"Description: {project.description}")
97
+ """
98
+ request = ProjectRequest(project_id=project_id)
99
+ response = self.get_projects(request)
100
+
101
+ if response.projects:
102
+ return response.projects[0]
103
+ return None
@@ -0,0 +1,128 @@
1
+ """Student API module for DataGSM OpenAPI SDK."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Optional
5
+
6
+ from ..models import Sex, SortDirection, Student, StudentResponse, StudentRole, StudentSortBy
7
+ from ._base import BaseApi
8
+
9
+
10
+ @dataclass
11
+ class StudentRequest:
12
+ """학생 조회 요청 파라미터 (Student Query Parameters).
13
+
14
+ Attributes:
15
+ student_id: Student ID for exact match
16
+ name: Student name for filtering
17
+ email: Email address for filtering
18
+ grade: Grade (1-3)
19
+ class_num: Class number
20
+ number: Student number within class
21
+ sex: Gender filter
22
+ role: Student role filter
23
+ dormitory_room: Dormitory room number
24
+ is_leave_school: Filter by leave school status
25
+ is_graduate: Filter by graduate status
26
+ page: Page number (default: 0)
27
+ size: Page size (default: 300)
28
+ sort_by: Sort field
29
+ sort_direction: Sort direction (default: ASC)
30
+ """
31
+
32
+ student_id: Optional[int] = None
33
+ name: Optional[str] = None
34
+ email: Optional[str] = None
35
+ grade: Optional[int] = None
36
+ class_num: Optional[int] = None
37
+ number: Optional[int] = None
38
+ sex: Optional[Sex] = None
39
+ role: Optional[StudentRole] = None
40
+ dormitory_room: Optional[int] = None
41
+ is_leave_school: Optional[bool] = None
42
+ is_graduate: Optional[bool] = None
43
+ page: int = 0
44
+ size: int = 300
45
+ sort_by: Optional[StudentSortBy] = None
46
+ sort_direction: SortDirection = SortDirection.ASC
47
+
48
+ def to_params(self) -> dict[str, Optional[object]]:
49
+ """Convert to query parameters dictionary.
50
+
51
+ Returns:
52
+ Dictionary of query parameters
53
+ """
54
+ params: dict[str, Optional[object]] = {
55
+ "studentId": self.student_id,
56
+ "name": self.name,
57
+ "email": self.email,
58
+ "grade": self.grade,
59
+ "classNum": self.class_num,
60
+ "number": self.number,
61
+ "sex": self.sex.value if self.sex else None,
62
+ "role": self.role.value if self.role else None,
63
+ "dormitoryRoom": self.dormitory_room,
64
+ "isLeaveSchool": self.is_leave_school,
65
+ "isGraduated": self.is_graduate,
66
+ "page": self.page,
67
+ "size": self.size,
68
+ "sortBy": self.sort_by.value if self.sort_by else None,
69
+ "sortDirection": self.sort_direction.value,
70
+ }
71
+ return params
72
+
73
+
74
+ class StudentApi(BaseApi):
75
+ """학생 데이터 API (Student Data API).
76
+
77
+ Provides methods for querying student information.
78
+ """
79
+
80
+ def get_students(
81
+ self, request: Optional[StudentRequest] = None
82
+ ) -> StudentResponse:
83
+ """학생 목록 조회 (Get Student List).
84
+
85
+ Query students with optional filtering, sorting, and pagination.
86
+
87
+ Args:
88
+ request: Query parameters (optional)
89
+
90
+ Returns:
91
+ Paginated student response
92
+
93
+ Example:
94
+ >>> api = StudentApi(http_client)
95
+ >>> # Get all students
96
+ >>> response = api.get_students()
97
+ >>> print(f"Total: {response.total_elements}")
98
+ >>>
99
+ >>> # Filter by grade
100
+ >>> request = StudentRequest(grade=1)
101
+ >>> first_graders = api.get_students(request)
102
+ """
103
+ req = request or StudentRequest()
104
+ return self._get("/v1/students", params=req.to_params(), response_type=StudentResponse)
105
+
106
+ def get_student(self, student_id: int) -> Optional[Student]:
107
+ """특정 학생 조회 (Get Specific Student).
108
+
109
+ Retrieve a single student by ID.
110
+
111
+ Args:
112
+ student_id: Student ID
113
+
114
+ Returns:
115
+ Student information if found, None otherwise
116
+
117
+ Example:
118
+ >>> api = StudentApi(http_client)
119
+ >>> student = api.get_student(123)
120
+ >>> if student:
121
+ ... print(f"Name: {student.name}")
122
+ """
123
+ request = StudentRequest(student_id=student_id)
124
+ response = self.get_students(request)
125
+
126
+ if response.students:
127
+ return response.students[0]
128
+ return None
@@ -0,0 +1,118 @@
1
+ """Main client class for DataGSM OpenAPI SDK."""
2
+
3
+ from typing import Any, Optional
4
+
5
+ from ._http import HttpClient
6
+ from .api import ClubApi, NeisApi, ProjectApi, StudentApi
7
+
8
+
9
+ class DataGsmClient:
10
+ """Main client for interacting with the DataGSM OpenAPI.
11
+
12
+ This is the main entry point for the SDK. Create an instance with your API key
13
+ and use it to access various API endpoints.
14
+
15
+ Example:
16
+ Basic usage::
17
+
18
+ with DataGsmClient(api_key="your-api-key") as client:
19
+ students = client.students.get_students()
20
+
21
+ Attributes:
22
+ base_url: Base URL for the API
23
+ timeout: Request timeout in seconds
24
+ """
25
+
26
+ DEFAULT_BASE_URL = "https://openapi.data.hellogsm.kr"
27
+
28
+ def __init__(
29
+ self,
30
+ api_key: str,
31
+ base_url: Optional[str] = None,
32
+ timeout: float = 30.0,
33
+ ) -> None:
34
+ """Initialize the DataGSM client.
35
+
36
+ Args:
37
+ api_key: Your DataGSM API key
38
+ base_url: Base URL for the API (default: https://openapi.data.hellogsm.kr)
39
+ timeout: Request timeout in seconds (default: 30.0)
40
+ """
41
+ self.base_url = base_url or self.DEFAULT_BASE_URL
42
+ self.timeout = timeout
43
+ self._http_client = HttpClient(
44
+ base_url=self.base_url,
45
+ api_key=api_key,
46
+ timeout=timeout,
47
+ )
48
+
49
+ # Initialize API modules
50
+ self._student_api = StudentApi(self._http_client)
51
+ self._club_api = ClubApi(self._http_client)
52
+ self._project_api = ProjectApi(self._http_client)
53
+ self._neis_api = NeisApi(self._http_client)
54
+
55
+ @property
56
+ def students(self) -> StudentApi:
57
+ """Access the Student API.
58
+
59
+ Returns:
60
+ StudentApi instance
61
+
62
+ Example:
63
+ >>> with DataGsmClient(api_key="key") as client:
64
+ ... students = client.students.get_students()
65
+ """
66
+ return self._student_api
67
+
68
+ @property
69
+ def clubs(self) -> ClubApi:
70
+ """Access the Club API.
71
+
72
+ Returns:
73
+ ClubApi instance
74
+
75
+ Example:
76
+ >>> with DataGsmClient(api_key="key") as client:
77
+ ... clubs = client.clubs.get_clubs()
78
+ """
79
+ return self._club_api
80
+
81
+ @property
82
+ def projects(self) -> ProjectApi:
83
+ """Access the Project API.
84
+
85
+ Returns:
86
+ ProjectApi instance
87
+
88
+ Example:
89
+ >>> with DataGsmClient(api_key="key") as client:
90
+ ... projects = client.projects.get_projects()
91
+ """
92
+ return self._project_api
93
+
94
+ @property
95
+ def neis(self) -> NeisApi:
96
+ """Access the NEIS API.
97
+
98
+ Returns:
99
+ NeisApi instance
100
+
101
+ Example:
102
+ >>> with DataGsmClient(api_key="key") as client:
103
+ ... meals = client.neis.get_meals()
104
+ """
105
+ return self._neis_api
106
+
107
+ def __enter__(self) -> "DataGsmClient":
108
+ """Enter context manager."""
109
+ self._http_client.__enter__()
110
+ return self
111
+
112
+ def __exit__(self, *args: Any) -> None:
113
+ """Exit context manager and close resources."""
114
+ self._http_client.__exit__(*args)
115
+
116
+ def close(self) -> None:
117
+ """Close the client and release resources."""
118
+ self._http_client.close()
@@ -0,0 +1,213 @@
1
+ """Exception classes for DataGSM OpenAPI SDK."""
2
+
3
+ from typing import Any, Optional
4
+
5
+
6
+ class DataGsmException(Exception):
7
+ """Base exception for all DataGSM API errors.
8
+
9
+ Attributes:
10
+ message: Human-readable error message
11
+ status_code: HTTP status code if available
12
+ response_body: Raw response body if available
13
+ """
14
+
15
+ def __init__(
16
+ self,
17
+ message: str,
18
+ status_code: Optional[int] = None,
19
+ response_body: Optional[dict[str, Any]] = None,
20
+ ) -> None:
21
+ """Initialize the exception.
22
+
23
+ Args:
24
+ message: Error message
25
+ status_code: HTTP status code
26
+ response_body: Raw response body
27
+ """
28
+ super().__init__(message)
29
+ self.message = message
30
+ self.status_code = status_code
31
+ self.response_body = response_body
32
+
33
+ def __str__(self) -> str:
34
+ """Return string representation of the exception."""
35
+ if self.status_code:
36
+ return f"[{self.status_code}] {self.message}"
37
+ return self.message
38
+
39
+ def __repr__(self) -> str:
40
+ """Return detailed representation of the exception."""
41
+ return (
42
+ f"{self.__class__.__name__}("
43
+ f"message={self.message!r}, "
44
+ f"status_code={self.status_code!r})"
45
+ )
46
+
47
+
48
+ class BadRequestException(DataGsmException):
49
+ """Exception raised for 400 Bad Request errors.
50
+
51
+ Indicates that the request was malformed or contained invalid parameters.
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ message: str = "Bad request",
57
+ response_body: Optional[dict[str, Any]] = None,
58
+ ) -> None:
59
+ """Initialize the exception.
60
+
61
+ Args:
62
+ message: Error message
63
+ response_body: Raw response body
64
+ """
65
+ super().__init__(message=message, status_code=400, response_body=response_body)
66
+
67
+
68
+ class UnauthorizedException(DataGsmException):
69
+ """Exception raised for 401 Unauthorized errors.
70
+
71
+ Indicates that the API key is missing or invalid.
72
+ """
73
+
74
+ def __init__(
75
+ self,
76
+ message: str = "Unauthorized: Invalid or missing API key",
77
+ response_body: Optional[dict[str, Any]] = None,
78
+ ) -> None:
79
+ """Initialize the exception.
80
+
81
+ Args:
82
+ message: Error message
83
+ response_body: Raw response body
84
+ """
85
+ super().__init__(message=message, status_code=401, response_body=response_body)
86
+
87
+
88
+ class ForbiddenException(DataGsmException):
89
+ """Exception raised for 403 Forbidden errors.
90
+
91
+ Indicates that the API key doesn't have permission for the requested resource.
92
+ """
93
+
94
+ def __init__(
95
+ self,
96
+ message: str = "Forbidden: Insufficient permissions",
97
+ response_body: Optional[dict[str, Any]] = None,
98
+ ) -> None:
99
+ """Initialize the exception.
100
+
101
+ Args:
102
+ message: Error message
103
+ response_body: Raw response body
104
+ """
105
+ super().__init__(message=message, status_code=403, response_body=response_body)
106
+
107
+
108
+ class NotFoundException(DataGsmException):
109
+ """Exception raised for 404 Not Found errors.
110
+
111
+ Indicates that the requested resource was not found.
112
+ """
113
+
114
+ def __init__(
115
+ self,
116
+ message: str = "Not found",
117
+ response_body: Optional[dict[str, Any]] = None,
118
+ ) -> None:
119
+ """Initialize the exception.
120
+
121
+ Args:
122
+ message: Error message
123
+ response_body: Raw response body
124
+ """
125
+ super().__init__(message=message, status_code=404, response_body=response_body)
126
+
127
+
128
+ class RateLimitException(DataGsmException):
129
+ """Exception raised for 429 Too Many Requests errors.
130
+
131
+ Indicates that the rate limit has been exceeded.
132
+ """
133
+
134
+ def __init__(
135
+ self,
136
+ message: str = "Rate limit exceeded",
137
+ response_body: Optional[dict[str, Any]] = None,
138
+ ) -> None:
139
+ """Initialize the exception.
140
+
141
+ Args:
142
+ message: Error message
143
+ response_body: Raw response body
144
+ """
145
+ super().__init__(message=message, status_code=429, response_body=response_body)
146
+
147
+
148
+ class ServerErrorException(DataGsmException):
149
+ """Exception raised for 5xx Server Error responses.
150
+
151
+ Indicates that the server encountered an error processing the request.
152
+ """
153
+
154
+ def __init__(
155
+ self,
156
+ message: str = "Internal server error",
157
+ status_code: int = 500,
158
+ response_body: Optional[dict[str, Any]] = None,
159
+ ) -> None:
160
+ """Initialize the exception.
161
+
162
+ Args:
163
+ message: Error message
164
+ status_code: HTTP status code (5xx)
165
+ response_body: Raw response body
166
+ """
167
+ super().__init__(
168
+ message=message,
169
+ status_code=status_code,
170
+ response_body=response_body,
171
+ )
172
+
173
+
174
+ class NetworkException(DataGsmException):
175
+ """Exception raised for network-related errors.
176
+
177
+ Indicates connection failures, timeouts, or other network issues.
178
+ """
179
+
180
+ def __init__(
181
+ self,
182
+ message: str,
183
+ original_exception: Optional[Exception] = None,
184
+ ) -> None:
185
+ """Initialize the exception.
186
+
187
+ Args:
188
+ message: Error message
189
+ original_exception: The underlying exception that caused this error
190
+ """
191
+ super().__init__(message=message)
192
+ self.original_exception = original_exception
193
+
194
+
195
+ class ValidationException(DataGsmException):
196
+ """Exception raised for data validation errors.
197
+
198
+ Indicates that response data failed validation against the expected schema.
199
+ """
200
+
201
+ def __init__(
202
+ self,
203
+ message: str,
204
+ validation_errors: Optional[list[dict[str, Any]]] = None,
205
+ ) -> None:
206
+ """Initialize the exception.
207
+
208
+ Args:
209
+ message: Error message
210
+ validation_errors: List of validation errors from Pydantic
211
+ """
212
+ super().__init__(message=message)
213
+ self.validation_errors = validation_errors or []