classcharts-api 1.0.0__tar.gz
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.
- classcharts_api-1.0.0/LICENSE +15 -0
- classcharts_api-1.0.0/PKG-INFO +19 -0
- classcharts_api-1.0.0/README.md +38 -0
- classcharts_api-1.0.0/pyproject.toml +38 -0
- classcharts_api-1.0.0/setup.cfg +4 -0
- classcharts_api-1.0.0/src/classcharts_api/__init__.py +14 -0
- classcharts_api-1.0.0/src/classcharts_api/base_client.py +336 -0
- classcharts_api-1.0.0/src/classcharts_api/const.py +6 -0
- classcharts_api-1.0.0/src/classcharts_api/exceptions.py +6 -0
- classcharts_api-1.0.0/src/classcharts_api/models.py +556 -0
- classcharts_api-1.0.0/src/classcharts_api/parent_client.py +271 -0
- classcharts_api-1.0.0/src/classcharts_api/student_client.py +122 -0
- classcharts_api-1.0.0/src/classcharts_api/utils.py +36 -0
- classcharts_api-1.0.0/src/classcharts_api.egg-info/PKG-INFO +19 -0
- classcharts_api-1.0.0/src/classcharts_api.egg-info/SOURCES.txt +20 -0
- classcharts_api-1.0.0/src/classcharts_api.egg-info/dependency_links.txt +1 -0
- classcharts_api-1.0.0/src/classcharts_api.egg-info/requires.txt +7 -0
- classcharts_api-1.0.0/src/classcharts_api.egg-info/top_level.txt +1 -0
- classcharts_api-1.0.0/tests/test_parent_client.py +161 -0
- classcharts_api-1.0.0/tests/test_parent_client_live.py +202 -0
- classcharts_api-1.0.0/tests/test_student_client.py +35 -0
- classcharts_api-1.0.0/tests/test_utils.py +51 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 James Cook
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
10
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
11
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
12
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
13
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
14
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
15
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: classcharts-api
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Async Python wrapper for the ClassCharts API
|
|
5
|
+
License: ISC
|
|
6
|
+
Keywords: classcharts,school,api,home-assistant
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: ISC License (ISCL)
|
|
9
|
+
Classifier: Framework :: AsyncIO
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: aiohttp>=3.9.0
|
|
14
|
+
Provides-Extra: dev
|
|
15
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
16
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
17
|
+
Requires-Dist: aioresponses>=0.7; extra == "dev"
|
|
18
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
19
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# classcharts-api
|
|
2
|
+
|
|
3
|
+
A Python port of [classcharts-api-js](https://github.com/classchartsapi/classcharts-api-js) by James Cook.
|
|
4
|
+
|
|
5
|
+
Provides an async Python client for the ClassCharts parent and student APIs.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install classcharts-api
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
import asyncio
|
|
17
|
+
from classcharts_api import ParentClient
|
|
18
|
+
|
|
19
|
+
async def main():
|
|
20
|
+
async with ParentClient("email@example.com", "password") as client:
|
|
21
|
+
await client.login()
|
|
22
|
+
pupils = client.pupils
|
|
23
|
+
homeworks = await client.get_homeworks_for_each_pupil(
|
|
24
|
+
display_date="due_date",
|
|
25
|
+
from_date="2026-01-01",
|
|
26
|
+
to_date="2026-07-01",
|
|
27
|
+
)
|
|
28
|
+
for pupil_id, resp in homeworks.items():
|
|
29
|
+
print(f"Pupil {pupil_id}: {len(resp['data'])} homework items")
|
|
30
|
+
|
|
31
|
+
asyncio.run(main())
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
ISC License — see [LICENSE](LICENSE).
|
|
37
|
+
|
|
38
|
+
This project is a Python port of [classcharts-api-js](https://github.com/classchartsapi/classcharts-api-js), copyright (c) 2022 James Cook, used under the ISC License.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "classcharts-api"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Async Python wrapper for the ClassCharts API"
|
|
9
|
+
license = { text = "ISC" }
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
keywords = ["classcharts", "school", "api", "home-assistant"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"License :: OSI Approved :: ISC License (ISCL)",
|
|
15
|
+
"Framework :: AsyncIO",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
]
|
|
18
|
+
dependencies = [
|
|
19
|
+
"aiohttp>=3.9.0",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
dev = [
|
|
24
|
+
"pytest>=8.0",
|
|
25
|
+
"pytest-asyncio>=0.23",
|
|
26
|
+
"aioresponses>=0.7",
|
|
27
|
+
"pytest-cov>=5.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
where = ["src"]
|
|
32
|
+
|
|
33
|
+
[tool.pytest.ini_options]
|
|
34
|
+
asyncio_mode = "auto"
|
|
35
|
+
testpaths = ["tests"]
|
|
36
|
+
|
|
37
|
+
[tool.ruff]
|
|
38
|
+
line-length = 100
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""ClassCharts API Python wrapper."""
|
|
2
|
+
|
|
3
|
+
from .parent_client import ParentClient
|
|
4
|
+
from .student_client import StudentClient
|
|
5
|
+
from .exceptions import ClassChartsAuthError, ClassChartsApiError
|
|
6
|
+
from . import models
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"ParentClient",
|
|
10
|
+
"StudentClient",
|
|
11
|
+
"ClassChartsAuthError",
|
|
12
|
+
"ClassChartsApiError",
|
|
13
|
+
"models",
|
|
14
|
+
]
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"""Base async client — shared logic for both ParentClient and StudentClient.
|
|
2
|
+
|
|
3
|
+
Mirrors src/core/baseClient.ts from classcharts-api-js.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import time
|
|
10
|
+
from abc import ABC, abstractmethod
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import aiohttp
|
|
14
|
+
|
|
15
|
+
from .const import PING_INTERVAL
|
|
16
|
+
from .exceptions import ClassChartsApiError
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class BaseClient(ABC):
|
|
20
|
+
"""Shared async HTTP client for ClassCharts."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, api_base: str) -> None:
|
|
23
|
+
self._api_base = api_base
|
|
24
|
+
self.student_id: int = 0
|
|
25
|
+
self.session_id: str = ""
|
|
26
|
+
self.auth_cookies: list[str] = []
|
|
27
|
+
self._last_ping: float = 0.0
|
|
28
|
+
self._session: aiohttp.ClientSession | None = None
|
|
29
|
+
|
|
30
|
+
# ------------------------------------------------------------------
|
|
31
|
+
# Session management
|
|
32
|
+
# ------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
def _get_session(self) -> aiohttp.ClientSession:
|
|
35
|
+
if self._session is None or self._session.closed:
|
|
36
|
+
self._session = aiohttp.ClientSession()
|
|
37
|
+
return self._session
|
|
38
|
+
|
|
39
|
+
async def close(self) -> None:
|
|
40
|
+
"""Close the underlying aiohttp session."""
|
|
41
|
+
if self._session and not self._session.closed:
|
|
42
|
+
await self._session.close()
|
|
43
|
+
|
|
44
|
+
async def __aenter__(self) -> "BaseClient":
|
|
45
|
+
return self
|
|
46
|
+
|
|
47
|
+
async def __aexit__(self, *_: Any) -> None:
|
|
48
|
+
await self.close()
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
async def login(self) -> None:
|
|
52
|
+
"""Authenticate with ClassCharts and store session credentials."""
|
|
53
|
+
|
|
54
|
+
# ------------------------------------------------------------------
|
|
55
|
+
# Session revalidation (ping)
|
|
56
|
+
# ------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
async def get_new_session_id(self) -> None:
|
|
59
|
+
"""Revalidate the session ID.
|
|
60
|
+
|
|
61
|
+
Called automatically when the session is older than PING_INTERVAL
|
|
62
|
+
seconds, and immediately after login for student clients.
|
|
63
|
+
Mirrors getNewSessionId() in baseClient.ts.
|
|
64
|
+
"""
|
|
65
|
+
data = aiohttp.FormData()
|
|
66
|
+
data.add_field("include_data", "true")
|
|
67
|
+
response = await self._make_authed_request(
|
|
68
|
+
f"{self._api_base}/ping",
|
|
69
|
+
method="POST",
|
|
70
|
+
data=data,
|
|
71
|
+
revalidate_token=False,
|
|
72
|
+
)
|
|
73
|
+
self.session_id = response["meta"]["session_id"]
|
|
74
|
+
self._last_ping = time.monotonic()
|
|
75
|
+
|
|
76
|
+
# ------------------------------------------------------------------
|
|
77
|
+
# Core request helper
|
|
78
|
+
# ------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
async def _make_authed_request(
|
|
81
|
+
self,
|
|
82
|
+
url: str,
|
|
83
|
+
method: str = "GET",
|
|
84
|
+
data: Any = None,
|
|
85
|
+
params: dict[str, str] | None = None,
|
|
86
|
+
revalidate_token: bool = True,
|
|
87
|
+
) -> dict[str, Any]:
|
|
88
|
+
"""Make an authenticated request to the ClassCharts API.
|
|
89
|
+
|
|
90
|
+
Mirrors makeAuthedRequest() in baseClient.ts.
|
|
91
|
+
"""
|
|
92
|
+
if not self.session_id:
|
|
93
|
+
raise ClassChartsApiError("No session ID — call login() first")
|
|
94
|
+
|
|
95
|
+
if revalidate_token and self._last_ping:
|
|
96
|
+
elapsed = time.monotonic() - self._last_ping
|
|
97
|
+
if elapsed + 5 > PING_INTERVAL:
|
|
98
|
+
await self.get_new_session_id()
|
|
99
|
+
|
|
100
|
+
headers = {
|
|
101
|
+
"Cookie": "; ".join(c.split(";")[0] for c in self.auth_cookies),
|
|
102
|
+
"Authorization": f"Basic {self.session_id}",
|
|
103
|
+
"User-Agent": "classcharts-api https://github.com/classchartsapi/classcharts-api-js",
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
session = self._get_session()
|
|
107
|
+
async with session.request(
|
|
108
|
+
method,
|
|
109
|
+
url,
|
|
110
|
+
headers=headers,
|
|
111
|
+
data=data,
|
|
112
|
+
params=params,
|
|
113
|
+
) as resp:
|
|
114
|
+
try:
|
|
115
|
+
payload: dict[str, Any] = await resp.json(content_type=None)
|
|
116
|
+
except Exception as exc:
|
|
117
|
+
text = await resp.text()
|
|
118
|
+
raise ClassChartsApiError(
|
|
119
|
+
f"Error parsing JSON. Response: {text}"
|
|
120
|
+
) from exc
|
|
121
|
+
|
|
122
|
+
if payload.get("success") == 0:
|
|
123
|
+
raise ClassChartsApiError(payload.get("error", "Unknown API error"))
|
|
124
|
+
|
|
125
|
+
return payload
|
|
126
|
+
|
|
127
|
+
# ------------------------------------------------------------------
|
|
128
|
+
# Shared endpoints
|
|
129
|
+
# ------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
async def get_student_info(self) -> dict[str, Any]:
|
|
132
|
+
"""Get general information about the current student.
|
|
133
|
+
|
|
134
|
+
Mirrors getStudentInfo() in baseClient.ts.
|
|
135
|
+
"""
|
|
136
|
+
data = aiohttp.FormData()
|
|
137
|
+
data.add_field("include_data", "true")
|
|
138
|
+
return await self._make_authed_request(
|
|
139
|
+
f"{self._api_base}/ping", method="POST", data=data
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
async def get_activity(
|
|
143
|
+
self,
|
|
144
|
+
from_date: str | None = None,
|
|
145
|
+
to_date: str | None = None,
|
|
146
|
+
last_id: str | None = None,
|
|
147
|
+
) -> dict[str, Any]:
|
|
148
|
+
"""Get the current student's activity (paginated).
|
|
149
|
+
|
|
150
|
+
Mirrors getActivity() in baseClient.ts.
|
|
151
|
+
"""
|
|
152
|
+
params: dict[str, str] = {}
|
|
153
|
+
if from_date:
|
|
154
|
+
params["from"] = from_date
|
|
155
|
+
if to_date:
|
|
156
|
+
params["to"] = to_date
|
|
157
|
+
if last_id:
|
|
158
|
+
params["last_id"] = last_id
|
|
159
|
+
return await self._make_authed_request(
|
|
160
|
+
f"{self._api_base}/activity/{self.student_id}", params=params
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
async def get_full_activity(
|
|
164
|
+
self, from_date: str, to_date: str
|
|
165
|
+
) -> list[dict[str, Any]]:
|
|
166
|
+
"""Get all activity between two dates, handling pagination automatically.
|
|
167
|
+
|
|
168
|
+
Mirrors getFullActivity() in baseClient.ts.
|
|
169
|
+
"""
|
|
170
|
+
data: list[dict[str, Any]] = []
|
|
171
|
+
prev_last: str | None = None
|
|
172
|
+
while True:
|
|
173
|
+
response = await self.get_activity(
|
|
174
|
+
from_date=from_date, to_date=to_date, last_id=prev_last
|
|
175
|
+
)
|
|
176
|
+
fragment = response.get("data") or []
|
|
177
|
+
if not fragment:
|
|
178
|
+
break
|
|
179
|
+
data.extend(fragment)
|
|
180
|
+
prev_last = str(fragment[-1]["id"])
|
|
181
|
+
return data
|
|
182
|
+
|
|
183
|
+
async def get_behaviour(
|
|
184
|
+
self,
|
|
185
|
+
from_date: str | None = None,
|
|
186
|
+
to_date: str | None = None,
|
|
187
|
+
) -> dict[str, Any]:
|
|
188
|
+
"""Get the current student's behaviour.
|
|
189
|
+
|
|
190
|
+
Mirrors getBehaviour() in baseClient.ts.
|
|
191
|
+
"""
|
|
192
|
+
params: dict[str, str] = {}
|
|
193
|
+
if from_date:
|
|
194
|
+
params["from"] = from_date
|
|
195
|
+
if to_date:
|
|
196
|
+
params["to"] = to_date
|
|
197
|
+
return await self._make_authed_request(
|
|
198
|
+
f"{self._api_base}/behaviour/{self.student_id}", params=params
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
async def get_homeworks(
|
|
202
|
+
self,
|
|
203
|
+
display_date: str | None = None,
|
|
204
|
+
from_date: str | None = None,
|
|
205
|
+
to_date: str | None = None,
|
|
206
|
+
) -> dict[str, Any]:
|
|
207
|
+
"""Get the current student's homeworks.
|
|
208
|
+
|
|
209
|
+
Mirrors getHomeworks() in baseClient.ts.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
display_date: "due_date" or "issue_date" (default: "issue_date")
|
|
213
|
+
from_date: Start date in YYYY-MM-DD format
|
|
214
|
+
to_date: End date in YYYY-MM-DD format
|
|
215
|
+
"""
|
|
216
|
+
params: dict[str, str] = {}
|
|
217
|
+
if display_date:
|
|
218
|
+
params["display_date"] = display_date
|
|
219
|
+
if from_date:
|
|
220
|
+
params["from"] = from_date
|
|
221
|
+
if to_date:
|
|
222
|
+
params["to"] = to_date
|
|
223
|
+
return await self._make_authed_request(
|
|
224
|
+
f"{self._api_base}/homeworks/{self.student_id}", params=params
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
async def get_lessons(self, date: str) -> dict[str, Any]:
|
|
228
|
+
"""Get the current student's lessons for a given date.
|
|
229
|
+
|
|
230
|
+
Mirrors getLessons() in baseClient.ts.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
date: Date in YYYY-MM-DD format
|
|
234
|
+
"""
|
|
235
|
+
return await self._make_authed_request(
|
|
236
|
+
f"{self._api_base}/timetable/{self.student_id}",
|
|
237
|
+
params={"date": date},
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
async def get_badges(self) -> dict[str, Any]:
|
|
241
|
+
"""Get the current student's earned badges.
|
|
242
|
+
|
|
243
|
+
Mirrors getBadges() in baseClient.ts.
|
|
244
|
+
"""
|
|
245
|
+
return await self._make_authed_request(
|
|
246
|
+
f"{self._api_base}/eventbadges/{self.student_id}"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
async def get_announcements(self) -> dict[str, Any]:
|
|
250
|
+
"""Get the current student's announcements.
|
|
251
|
+
|
|
252
|
+
Mirrors getAnnouncements() in baseClient.ts.
|
|
253
|
+
"""
|
|
254
|
+
return await self._make_authed_request(
|
|
255
|
+
f"{self._api_base}/announcements/{self.student_id}"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
async def get_detentions(self) -> dict[str, Any]:
|
|
259
|
+
"""Get the current student's detentions.
|
|
260
|
+
|
|
261
|
+
Mirrors getDetentions() in baseClient.ts.
|
|
262
|
+
"""
|
|
263
|
+
return await self._make_authed_request(
|
|
264
|
+
f"{self._api_base}/detentions/{self.student_id}"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
async def get_attendance(
|
|
268
|
+
self,
|
|
269
|
+
from_date: str | None = None,
|
|
270
|
+
to_date: str | None = None,
|
|
271
|
+
) -> dict[str, Any]:
|
|
272
|
+
"""Get the current student's attendance.
|
|
273
|
+
|
|
274
|
+
Mirrors getAttendance() in baseClient.ts.
|
|
275
|
+
"""
|
|
276
|
+
params: dict[str, str] = {}
|
|
277
|
+
if from_date:
|
|
278
|
+
params["from"] = from_date
|
|
279
|
+
if to_date:
|
|
280
|
+
params["to"] = to_date
|
|
281
|
+
return await self._make_authed_request(
|
|
282
|
+
f"{self._api_base}/attendance/{self.student_id}", params=params
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
async def get_pupil_fields(self) -> dict[str, Any]:
|
|
286
|
+
"""Get the current student's custom fields.
|
|
287
|
+
|
|
288
|
+
Mirrors getPupilFields() in baseClient.ts.
|
|
289
|
+
"""
|
|
290
|
+
return await self._make_authed_request(
|
|
291
|
+
f"{self._api_base}/customfields/{self.student_id}"
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
async def get_classes(self) -> dict[str, Any]:
|
|
295
|
+
"""Get the classes the current student is enrolled in."""
|
|
296
|
+
return await self._make_authed_request(
|
|
297
|
+
f"{self._api_base}/classes/{self.student_id}"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
async def get_academic_reports(self) -> dict[str, Any]:
|
|
301
|
+
"""List available academic reports for the current student."""
|
|
302
|
+
return await self._make_authed_request(
|
|
303
|
+
f"{self._api_base}/getacademicreports"
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
async def get_academic_report(self, report_id: int) -> dict[str, Any]:
|
|
307
|
+
"""Get a specific academic report by ID."""
|
|
308
|
+
return await self._make_authed_request(
|
|
309
|
+
f"{self._api_base}/getacademicreport/{report_id}"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
async def get_report_cards(self) -> dict[str, Any]:
|
|
313
|
+
"""List on-report cards for the current student."""
|
|
314
|
+
data = aiohttp.FormData()
|
|
315
|
+
data.add_field("pupil_id", str(self.student_id))
|
|
316
|
+
return await self._make_authed_request(
|
|
317
|
+
f"{self._api_base}/getpupilreportcards", method="POST", data=data
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
async def get_report_card(self, report_card_id: int) -> dict[str, Any]:
|
|
321
|
+
"""Get a specific on-report card by ID."""
|
|
322
|
+
return await self._make_authed_request(
|
|
323
|
+
f"{self._api_base}/getpupilreportcard/{report_card_id}"
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
async def get_report_card_summary_comment(self, report_card_id: int) -> dict[str, Any]:
|
|
327
|
+
"""Get the summary comment for a specific on-report card."""
|
|
328
|
+
return await self._make_authed_request(
|
|
329
|
+
f"{self._api_base}/getpupilreportcardsummarycomment/{report_card_id}"
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
async def get_report_card_target(self, report_card_id: int) -> dict[str, Any]:
|
|
333
|
+
"""Get the target for a specific on-report card."""
|
|
334
|
+
return await self._make_authed_request(
|
|
335
|
+
f"{self._api_base}/getpupilreportcardtarget/{report_card_id}"
|
|
336
|
+
)
|