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.
- datagsm_openapi/__init__.py +61 -0
- datagsm_openapi/_http.py +291 -0
- datagsm_openapi/_json.py +53 -0
- datagsm_openapi/api/__init__.py +18 -0
- datagsm_openapi/api/_base.py +73 -0
- datagsm_openapi/api/club.py +106 -0
- datagsm_openapi/api/neis.py +141 -0
- datagsm_openapi/api/project.py +103 -0
- datagsm_openapi/api/student.py +128 -0
- datagsm_openapi/client.py +118 -0
- datagsm_openapi/exceptions.py +213 -0
- datagsm_openapi/models/__init__.py +41 -0
- datagsm_openapi/models/_common.py +37 -0
- datagsm_openapi/models/club.py +76 -0
- datagsm_openapi/models/enums.py +82 -0
- datagsm_openapi/models/neis.py +102 -0
- datagsm_openapi/models/project.py +76 -0
- datagsm_openapi/models/student.py +79 -0
- datagsm_openapi/py.typed +1 -0
- datagsm_openapi_sdk-1.0.0b1.dist-info/METADATA +55 -0
- datagsm_openapi_sdk-1.0.0b1.dist-info/RECORD +24 -0
- datagsm_openapi_sdk-1.0.0b1.dist-info/WHEEL +5 -0
- datagsm_openapi_sdk-1.0.0b1.dist-info/licenses/LICENSE +21 -0
- datagsm_openapi_sdk-1.0.0b1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""DataGSM OpenAPI SDK for Python.
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the DataGSM OpenAPI service.
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
Basic usage::
|
|
7
|
+
|
|
8
|
+
from datagsm_openapi import DataGsmClient
|
|
9
|
+
|
|
10
|
+
with DataGsmClient(api_key="your-api-key") as client:
|
|
11
|
+
students = client.students.get_students()
|
|
12
|
+
print(f"Total students: {students.total_elements}")
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from .api import (
|
|
16
|
+
ClubApi,
|
|
17
|
+
ClubRequest,
|
|
18
|
+
MealRequest,
|
|
19
|
+
NeisApi,
|
|
20
|
+
ProjectApi,
|
|
21
|
+
ProjectRequest,
|
|
22
|
+
ScheduleRequest,
|
|
23
|
+
StudentApi,
|
|
24
|
+
StudentRequest,
|
|
25
|
+
)
|
|
26
|
+
from .client import DataGsmClient
|
|
27
|
+
from .exceptions import (
|
|
28
|
+
BadRequestException,
|
|
29
|
+
DataGsmException,
|
|
30
|
+
ForbiddenException,
|
|
31
|
+
NetworkException,
|
|
32
|
+
NotFoundException,
|
|
33
|
+
RateLimitException,
|
|
34
|
+
ServerErrorException,
|
|
35
|
+
UnauthorizedException,
|
|
36
|
+
ValidationException,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
__version__ = "0.1.0"
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"BadRequestException",
|
|
43
|
+
"ClubApi",
|
|
44
|
+
"ClubRequest",
|
|
45
|
+
"DataGsmClient",
|
|
46
|
+
"DataGsmException",
|
|
47
|
+
"ForbiddenException",
|
|
48
|
+
"MealRequest",
|
|
49
|
+
"NeisApi",
|
|
50
|
+
"NetworkException",
|
|
51
|
+
"NotFoundException",
|
|
52
|
+
"ProjectApi",
|
|
53
|
+
"ProjectRequest",
|
|
54
|
+
"RateLimitException",
|
|
55
|
+
"ScheduleRequest",
|
|
56
|
+
"ServerErrorException",
|
|
57
|
+
"StudentApi",
|
|
58
|
+
"StudentRequest",
|
|
59
|
+
"UnauthorizedException",
|
|
60
|
+
"ValidationException",
|
|
61
|
+
]
|
datagsm_openapi/_http.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"""HTTP client abstraction layer for DataGSM OpenAPI SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
from .exceptions import (
|
|
8
|
+
BadRequestException,
|
|
9
|
+
DataGsmException,
|
|
10
|
+
ForbiddenException,
|
|
11
|
+
NetworkException,
|
|
12
|
+
NotFoundException,
|
|
13
|
+
RateLimitException,
|
|
14
|
+
ServerErrorException,
|
|
15
|
+
UnauthorizedException,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HttpClient:
|
|
20
|
+
"""HTTP client wrapper around httpx with error handling.
|
|
21
|
+
|
|
22
|
+
This class provides a clean interface for making HTTP requests to the DataGSM API
|
|
23
|
+
with automatic error handling and exception mapping.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
base_url: Base URL for the API
|
|
27
|
+
api_key: API key for authentication
|
|
28
|
+
timeout: Request timeout in seconds
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
base_url: str,
|
|
34
|
+
api_key: str,
|
|
35
|
+
timeout: float = 30.0,
|
|
36
|
+
) -> None:
|
|
37
|
+
"""Initialize the HTTP client.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
base_url: Base URL for the API
|
|
41
|
+
api_key: API key for authentication
|
|
42
|
+
timeout: Request timeout in seconds (default: 30.0)
|
|
43
|
+
"""
|
|
44
|
+
self.base_url = base_url.rstrip("/")
|
|
45
|
+
self.api_key = api_key
|
|
46
|
+
self.timeout = timeout
|
|
47
|
+
self._client: Optional[httpx.Client] = None
|
|
48
|
+
|
|
49
|
+
def __enter__(self) -> "HttpClient":
|
|
50
|
+
"""Enter context manager."""
|
|
51
|
+
self._client = httpx.Client(
|
|
52
|
+
base_url=self.base_url,
|
|
53
|
+
timeout=self.timeout,
|
|
54
|
+
headers=self._get_headers(),
|
|
55
|
+
)
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
def __exit__(self, *args: Any) -> None:
|
|
59
|
+
"""Exit context manager and close the client."""
|
|
60
|
+
self.close()
|
|
61
|
+
|
|
62
|
+
def _get_headers(self) -> dict[str, str]:
|
|
63
|
+
"""Get common headers for all requests.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Dictionary of HTTP headers
|
|
67
|
+
"""
|
|
68
|
+
return {
|
|
69
|
+
"X-API-KEY": self.api_key,
|
|
70
|
+
"Accept": "application/json",
|
|
71
|
+
"User-Agent": "datagsm-openapi-sdk-python/0.1.0",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
def _get_client(self) -> httpx.Client:
|
|
75
|
+
"""Get or create the HTTP client.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
The httpx.Client instance
|
|
79
|
+
"""
|
|
80
|
+
if self._client is None:
|
|
81
|
+
self._client = httpx.Client(
|
|
82
|
+
base_url=self.base_url,
|
|
83
|
+
timeout=self.timeout,
|
|
84
|
+
headers=self._get_headers(),
|
|
85
|
+
)
|
|
86
|
+
return self._client
|
|
87
|
+
|
|
88
|
+
def _handle_error(self, response: httpx.Response) -> None:
|
|
89
|
+
"""Handle HTTP error responses.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
response: The HTTP response object
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
BadRequestException: For 400 errors
|
|
96
|
+
UnauthorizedException: For 401 errors
|
|
97
|
+
ForbiddenException: For 403 errors
|
|
98
|
+
NotFoundException: For 404 errors
|
|
99
|
+
RateLimitException: For 429 errors
|
|
100
|
+
ServerErrorException: For 5xx errors
|
|
101
|
+
DataGsmException: For other errors
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
error_body = response.json()
|
|
105
|
+
error_message = error_body.get("message", response.text)
|
|
106
|
+
except Exception:
|
|
107
|
+
error_body = None
|
|
108
|
+
error_message = response.text or f"HTTP {response.status_code} error"
|
|
109
|
+
|
|
110
|
+
status_code = response.status_code
|
|
111
|
+
|
|
112
|
+
if status_code == 400:
|
|
113
|
+
raise BadRequestException(message=error_message, response_body=error_body)
|
|
114
|
+
elif status_code == 401:
|
|
115
|
+
raise UnauthorizedException(message=error_message, response_body=error_body)
|
|
116
|
+
elif status_code == 403:
|
|
117
|
+
raise ForbiddenException(message=error_message, response_body=error_body)
|
|
118
|
+
elif status_code == 404:
|
|
119
|
+
raise NotFoundException(message=error_message, response_body=error_body)
|
|
120
|
+
elif status_code == 429:
|
|
121
|
+
raise RateLimitException(message=error_message, response_body=error_body)
|
|
122
|
+
elif 500 <= status_code < 600:
|
|
123
|
+
raise ServerErrorException(
|
|
124
|
+
message=error_message,
|
|
125
|
+
status_code=status_code,
|
|
126
|
+
response_body=error_body,
|
|
127
|
+
)
|
|
128
|
+
else:
|
|
129
|
+
raise DataGsmException(
|
|
130
|
+
message=error_message,
|
|
131
|
+
status_code=status_code,
|
|
132
|
+
response_body=error_body,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def get(
|
|
136
|
+
self,
|
|
137
|
+
path: str,
|
|
138
|
+
params: Optional[dict[str, Any]] = None,
|
|
139
|
+
) -> dict[str, Any]:
|
|
140
|
+
"""Make a GET request.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
path: API endpoint path (e.g., "/students")
|
|
144
|
+
params: Query parameters
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
JSON response as a dictionary
|
|
148
|
+
|
|
149
|
+
Raises:
|
|
150
|
+
NetworkException: For connection or timeout errors
|
|
151
|
+
DataGsmException: For API errors
|
|
152
|
+
"""
|
|
153
|
+
try:
|
|
154
|
+
client = self._get_client()
|
|
155
|
+
response = client.get(path, params=params)
|
|
156
|
+
response.raise_for_status()
|
|
157
|
+
return response.json() # type: ignore[no-any-return]
|
|
158
|
+
except httpx.HTTPStatusError as e:
|
|
159
|
+
self._handle_error(e.response)
|
|
160
|
+
raise # This line will never be reached, but makes mypy happy
|
|
161
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
162
|
+
raise NetworkException(
|
|
163
|
+
message=f"Network error: {e!s}",
|
|
164
|
+
original_exception=e,
|
|
165
|
+
) from e
|
|
166
|
+
except httpx.HTTPError as e:
|
|
167
|
+
raise NetworkException(
|
|
168
|
+
message=f"HTTP error: {e!s}",
|
|
169
|
+
original_exception=e,
|
|
170
|
+
) from e
|
|
171
|
+
|
|
172
|
+
def post(
|
|
173
|
+
self,
|
|
174
|
+
path: str,
|
|
175
|
+
json: Optional[dict[str, Any]] = None,
|
|
176
|
+
params: Optional[dict[str, Any]] = None,
|
|
177
|
+
) -> dict[str, Any]:
|
|
178
|
+
"""Make a POST request.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
path: API endpoint path
|
|
182
|
+
json: JSON request body
|
|
183
|
+
params: Query parameters
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
JSON response as a dictionary
|
|
187
|
+
|
|
188
|
+
Raises:
|
|
189
|
+
NetworkException: For connection or timeout errors
|
|
190
|
+
DataGsmException: For API errors
|
|
191
|
+
"""
|
|
192
|
+
try:
|
|
193
|
+
client = self._get_client()
|
|
194
|
+
response = client.post(path, json=json, params=params)
|
|
195
|
+
response.raise_for_status()
|
|
196
|
+
return response.json() # type: ignore[no-any-return]
|
|
197
|
+
except httpx.HTTPStatusError as e:
|
|
198
|
+
self._handle_error(e.response)
|
|
199
|
+
raise
|
|
200
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
201
|
+
raise NetworkException(
|
|
202
|
+
message=f"Network error: {e!s}",
|
|
203
|
+
original_exception=e,
|
|
204
|
+
) from e
|
|
205
|
+
except httpx.HTTPError as e:
|
|
206
|
+
raise NetworkException(
|
|
207
|
+
message=f"HTTP error: {e!s}",
|
|
208
|
+
original_exception=e,
|
|
209
|
+
) from e
|
|
210
|
+
|
|
211
|
+
def put(
|
|
212
|
+
self,
|
|
213
|
+
path: str,
|
|
214
|
+
json: Optional[dict[str, Any]] = None,
|
|
215
|
+
params: Optional[dict[str, Any]] = None,
|
|
216
|
+
) -> dict[str, Any]:
|
|
217
|
+
"""Make a PUT request.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
path: API endpoint path
|
|
221
|
+
json: JSON request body
|
|
222
|
+
params: Query parameters
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
JSON response as a dictionary
|
|
226
|
+
|
|
227
|
+
Raises:
|
|
228
|
+
NetworkException: For connection or timeout errors
|
|
229
|
+
DataGsmException: For API errors
|
|
230
|
+
"""
|
|
231
|
+
try:
|
|
232
|
+
client = self._get_client()
|
|
233
|
+
response = client.put(path, json=json, params=params)
|
|
234
|
+
response.raise_for_status()
|
|
235
|
+
return response.json() # type: ignore[no-any-return]
|
|
236
|
+
except httpx.HTTPStatusError as e:
|
|
237
|
+
self._handle_error(e.response)
|
|
238
|
+
raise
|
|
239
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
240
|
+
raise NetworkException(
|
|
241
|
+
message=f"Network error: {e!s}",
|
|
242
|
+
original_exception=e,
|
|
243
|
+
) from e
|
|
244
|
+
except httpx.HTTPError as e:
|
|
245
|
+
raise NetworkException(
|
|
246
|
+
message=f"HTTP error: {e!s}",
|
|
247
|
+
original_exception=e,
|
|
248
|
+
) from e
|
|
249
|
+
|
|
250
|
+
def delete(
|
|
251
|
+
self,
|
|
252
|
+
path: str,
|
|
253
|
+
params: Optional[dict[str, Any]] = None,
|
|
254
|
+
) -> dict[str, Any]:
|
|
255
|
+
"""Make a DELETE request.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
path: API endpoint path
|
|
259
|
+
params: Query parameters
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
JSON response as a dictionary
|
|
263
|
+
|
|
264
|
+
Raises:
|
|
265
|
+
NetworkException: For connection or timeout errors
|
|
266
|
+
DataGsmException: For API errors
|
|
267
|
+
"""
|
|
268
|
+
try:
|
|
269
|
+
client = self._get_client()
|
|
270
|
+
response = client.delete(path, params=params)
|
|
271
|
+
response.raise_for_status()
|
|
272
|
+
return response.json() # type: ignore[no-any-return]
|
|
273
|
+
except httpx.HTTPStatusError as e:
|
|
274
|
+
self._handle_error(e.response)
|
|
275
|
+
raise
|
|
276
|
+
except (httpx.ConnectError, httpx.TimeoutException) as e:
|
|
277
|
+
raise NetworkException(
|
|
278
|
+
message=f"Network error: {e!s}",
|
|
279
|
+
original_exception=e,
|
|
280
|
+
) from e
|
|
281
|
+
except httpx.HTTPError as e:
|
|
282
|
+
raise NetworkException(
|
|
283
|
+
message=f"HTTP error: {e!s}",
|
|
284
|
+
original_exception=e,
|
|
285
|
+
) from e
|
|
286
|
+
|
|
287
|
+
def close(self) -> None:
|
|
288
|
+
"""Close the HTTP client and release resources."""
|
|
289
|
+
if self._client is not None:
|
|
290
|
+
self._client.close()
|
|
291
|
+
self._client = None
|
datagsm_openapi/_json.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""JSON serialization utilities for DataGSM OpenAPI SDK."""
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def serialize_date(value: date) -> str:
|
|
8
|
+
"""Serialize a date to ISO format string.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
value: Date object to serialize
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
ISO format date string (YYYY-MM-DD)
|
|
15
|
+
"""
|
|
16
|
+
return value.isoformat()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def serialize_datetime(value: datetime) -> str:
|
|
20
|
+
"""Serialize a datetime to ISO format string.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
value: Datetime object to serialize
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
ISO format datetime string
|
|
27
|
+
"""
|
|
28
|
+
return value.isoformat()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def clean_params(params: dict[str, Any]) -> dict[str, Any]:
|
|
32
|
+
"""Clean query parameters by removing None values and serializing dates.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
params: Dictionary of query parameters
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Cleaned dictionary with None values removed and dates serialized
|
|
39
|
+
"""
|
|
40
|
+
cleaned: dict[str, Any] = {}
|
|
41
|
+
for key, value in params.items():
|
|
42
|
+
if value is None:
|
|
43
|
+
continue
|
|
44
|
+
if isinstance(value, datetime):
|
|
45
|
+
cleaned[key] = serialize_datetime(value)
|
|
46
|
+
elif isinstance(value, date):
|
|
47
|
+
cleaned[key] = serialize_date(value)
|
|
48
|
+
elif isinstance(value, bool):
|
|
49
|
+
# Convert boolean to lowercase string for query params
|
|
50
|
+
cleaned[key] = str(value).lower()
|
|
51
|
+
else:
|
|
52
|
+
cleaned[key] = value
|
|
53
|
+
return cleaned
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""API modules for DataGSM OpenAPI SDK."""
|
|
2
|
+
|
|
3
|
+
from .club import ClubApi, ClubRequest
|
|
4
|
+
from .neis import MealRequest, NeisApi, ScheduleRequest
|
|
5
|
+
from .project import ProjectApi, ProjectRequest
|
|
6
|
+
from .student import StudentApi, StudentRequest
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"ClubApi",
|
|
10
|
+
"ClubRequest",
|
|
11
|
+
"MealRequest",
|
|
12
|
+
"NeisApi",
|
|
13
|
+
"ProjectApi",
|
|
14
|
+
"ProjectRequest",
|
|
15
|
+
"ScheduleRequest",
|
|
16
|
+
"StudentApi",
|
|
17
|
+
"StudentRequest",
|
|
18
|
+
]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Base API class for DataGSM OpenAPI SDK."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional, Type, TypeVar, cast
|
|
4
|
+
|
|
5
|
+
from pydantic import ValidationError
|
|
6
|
+
|
|
7
|
+
from .._http import HttpClient
|
|
8
|
+
from .._json import clean_params
|
|
9
|
+
from ..exceptions import ValidationException
|
|
10
|
+
from ..models import CommonApiResponse
|
|
11
|
+
|
|
12
|
+
T = TypeVar("T")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseApi:
|
|
16
|
+
"""Base class for all API modules.
|
|
17
|
+
|
|
18
|
+
Provides common functionality for making HTTP requests and parsing responses.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, http_client: HttpClient) -> None:
|
|
22
|
+
"""Initialize the base API.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
http_client: HTTP client instance
|
|
26
|
+
"""
|
|
27
|
+
self._http = http_client
|
|
28
|
+
|
|
29
|
+
def _get(
|
|
30
|
+
self,
|
|
31
|
+
path: str,
|
|
32
|
+
params: Optional[dict[str, Any]] = None,
|
|
33
|
+
response_type: Optional[Type[T]] = None,
|
|
34
|
+
) -> T:
|
|
35
|
+
"""Make a GET request and parse the response.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
path: API endpoint path
|
|
39
|
+
params: Query parameters
|
|
40
|
+
response_type: Pydantic model class for response data
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Parsed response data
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
ValidationException: If response validation fails
|
|
47
|
+
"""
|
|
48
|
+
# Clean params (remove None values, serialize dates, etc.)
|
|
49
|
+
clean = clean_params(params) if params else None
|
|
50
|
+
|
|
51
|
+
# Make HTTP request
|
|
52
|
+
response_data = self._http.get(path, params=clean)
|
|
53
|
+
|
|
54
|
+
# If no response type specified, return raw data
|
|
55
|
+
if response_type is None:
|
|
56
|
+
return response_data # type: ignore[return-value]
|
|
57
|
+
|
|
58
|
+
# Parse as CommonApiResponse
|
|
59
|
+
try:
|
|
60
|
+
wrapped_response = CommonApiResponse[response_type].model_validate( # type: ignore[valid-type]
|
|
61
|
+
response_data
|
|
62
|
+
)
|
|
63
|
+
if wrapped_response.data is None:
|
|
64
|
+
raise ValidationException(
|
|
65
|
+
message="Response data is None",
|
|
66
|
+
validation_errors=[],
|
|
67
|
+
)
|
|
68
|
+
return cast(T, wrapped_response.data)
|
|
69
|
+
except ValidationError as e:
|
|
70
|
+
raise ValidationException(
|
|
71
|
+
message=f"Response validation failed: {e!s}",
|
|
72
|
+
validation_errors=cast(list[dict[str, Any]], e.errors()),
|
|
73
|
+
) from e
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Club API module for DataGSM OpenAPI SDK."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from ..models import ClubDetail, ClubResponse, ClubSortBy, ClubType, SortDirection
|
|
7
|
+
from ._base import BaseApi
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class ClubRequest:
|
|
12
|
+
"""동아리 조회 요청 파라미터 (Club Query Parameters).
|
|
13
|
+
|
|
14
|
+
Attributes:
|
|
15
|
+
club_id: Club ID for exact match
|
|
16
|
+
club_name: Club name for filtering
|
|
17
|
+
club_type: Club type filter (MAJOR_CLUB, JOB_CLUB, AUTONOMOUS_CLUB)
|
|
18
|
+
page: Page number (default: 0)
|
|
19
|
+
size: Page size (default: 100)
|
|
20
|
+
include_leader_in_participants: Include leader in participants list (default: False)
|
|
21
|
+
sort_by: Sort field
|
|
22
|
+
sort_direction: Sort direction (default: ASC)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
club_id: Optional[int] = None
|
|
26
|
+
club_name: Optional[str] = None
|
|
27
|
+
club_type: Optional[ClubType] = None
|
|
28
|
+
page: int = 0
|
|
29
|
+
size: int = 100
|
|
30
|
+
include_leader_in_participants: bool = False
|
|
31
|
+
sort_by: Optional[ClubSortBy] = None
|
|
32
|
+
sort_direction: SortDirection = SortDirection.ASC
|
|
33
|
+
|
|
34
|
+
def to_params(self) -> dict[str, Optional[object]]:
|
|
35
|
+
"""Convert to query parameters dictionary.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dictionary of query parameters
|
|
39
|
+
"""
|
|
40
|
+
params: dict[str, Optional[object]] = {
|
|
41
|
+
"clubId": self.club_id,
|
|
42
|
+
"clubName": self.club_name,
|
|
43
|
+
"clubType": self.club_type.value if self.club_type else None,
|
|
44
|
+
"page": self.page,
|
|
45
|
+
"size": self.size,
|
|
46
|
+
"includeLeaderInParticipants": self.include_leader_in_participants,
|
|
47
|
+
"sortBy": self.sort_by.value if self.sort_by else None,
|
|
48
|
+
"sortDirection": self.sort_direction.value,
|
|
49
|
+
}
|
|
50
|
+
return params
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ClubApi(BaseApi):
|
|
54
|
+
"""동아리 데이터 API (Club Data API).
|
|
55
|
+
|
|
56
|
+
Provides methods for querying club information.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def get_clubs(self, request: Optional[ClubRequest] = None) -> ClubResponse:
|
|
60
|
+
"""동아리 목록 조회 (Get Club List).
|
|
61
|
+
|
|
62
|
+
Query clubs with optional filtering, sorting, and pagination.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
request: Query parameters (optional)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Paginated club response
|
|
69
|
+
|
|
70
|
+
Example:
|
|
71
|
+
>>> api = ClubApi(http_client)
|
|
72
|
+
>>> # Get all clubs
|
|
73
|
+
>>> response = api.get_clubs()
|
|
74
|
+
>>> print(f"Total: {response.total_elements}")
|
|
75
|
+
>>>
|
|
76
|
+
>>> # Filter by type
|
|
77
|
+
>>> request = ClubRequest(club_type=ClubType.MAJOR_CLUB)
|
|
78
|
+
>>> major_clubs = api.get_clubs(request)
|
|
79
|
+
"""
|
|
80
|
+
req = request or ClubRequest()
|
|
81
|
+
return self._get("/v1/clubs", params=req.to_params(), response_type=ClubResponse)
|
|
82
|
+
|
|
83
|
+
def get_club(self, club_id: int) -> Optional[ClubDetail]:
|
|
84
|
+
"""특정 동아리 조회 (Get Specific Club).
|
|
85
|
+
|
|
86
|
+
Retrieve a single club by ID with detailed information.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
club_id: Club ID
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Club detail information if found, None otherwise
|
|
93
|
+
|
|
94
|
+
Example:
|
|
95
|
+
>>> api = ClubApi(http_client)
|
|
96
|
+
>>> club = api.get_club(1)
|
|
97
|
+
>>> if club:
|
|
98
|
+
... print(f"Club: {club.name}")
|
|
99
|
+
... print(f"Leader: {club.leader.name}")
|
|
100
|
+
"""
|
|
101
|
+
request = ClubRequest(club_id=club_id)
|
|
102
|
+
response = self.get_clubs(request)
|
|
103
|
+
|
|
104
|
+
if response.clubs:
|
|
105
|
+
return response.clubs[0]
|
|
106
|
+
return None
|