eduskit 0.1.4__tar.gz → 0.1.5__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eduskit
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: Eduskit B-end server SDK for classroom and whiteboard APIs
5
5
  Author: Eduskit
6
6
  License-Expression: Apache-2.0
@@ -21,7 +21,7 @@ class _Auth:
21
21
  expires_in = input.get("expiresIn", 3600)
22
22
  token, expires_at = sign_hs256(self._app_id, self._app_secret, user_id,
23
23
  "eduskit", "eduskit-room", expires_in,
24
- {"app_id": self._app_id, "room_id": room_id, "role": role, "source": "server_sdk"},
24
+ {"app_id": self._app_id, "room_id": room_id, "role": role, "source": "server_sdk", "access_generation": None},
25
25
  "whiteboard")
26
26
  return {"token": token, "appId": self._app_id, "roomId": room_id, "userId": user_id,
27
27
  "role": role, "expiresIn": expires_in, "expiresAt": iso_time(expires_at)}
@@ -93,12 +93,54 @@ class _Files:
93
93
  return self._http.request("GET", '/v1/files/convert' + "?" + urlencode({'jobId': job_id}))
94
94
 
95
95
 
96
+ class _Rooms:
97
+ def __init__(self, http: HttpTransport) -> None:
98
+ self._http = http
99
+
100
+ def schedule_private_room_writes(self, room_id: str, request_id: str, opens_at: str, closes_at: str) -> Any:
101
+ return self._http.request("POST", "/v1/rooms/private/write-window", {"roomId": room_id, "requestId": request_id, "opensAt": opens_at, "closesAt": closes_at})
102
+
103
+ def provision_private_room(self, room_id: str, assignment_id: str) -> Any:
104
+ return self._http.request("POST", "/v1/rooms/private", {"roomId": room_id, "assignmentId": assignment_id})
105
+
106
+ def initialize_private_workspace(self, room_id: str, assignment_id: str, source_snapshot_id: str | None) -> Any:
107
+ return self._http.request("POST", "/v1/rooms/private/initializations", {"roomId": room_id, "assignmentId": assignment_id, "sourceSnapshotId": source_snapshot_id})
108
+
109
+ def get_private_workspace_initialization(self, room_id: str) -> Any:
110
+ return self._http.request("POST", "/v1/rooms/private/initializations/query", {"roomId": room_id})
111
+
112
+ def change_private_room_grant(self, room_id: str, **input: Any) -> Any:
113
+ return self._http.request("POST", "/v1/rooms/private/grants", {**input, "roomId": room_id})
114
+
115
+ def get_private_room_access(self, room_id: str, user_id: str) -> Any:
116
+ return self._http.request("POST", "/v1/rooms/private/access/query", {"roomId": room_id, "userId": user_id})
117
+
118
+ def issue_private_room_token(self, room_id: str, **input: Any) -> Any:
119
+ return self._http.request("POST", "/v1/rooms/private/token", {**input, "roomId": room_id})
120
+
121
+ def seal_private_room(self, room_id: str) -> Any:
122
+ return self._http.request("POST", "/v1/rooms/private/seal", {"roomId": room_id})
123
+
124
+ def create_frozen_snapshot(self, room_id: str, snapshot_id: str) -> Any:
125
+ return self._http.request("POST", "/v1/rooms/private/snapshots", {"roomId": room_id, "snapshotId": snapshot_id})
126
+
127
+ def get_frozen_snapshot(self, room_id: str, snapshot_id: str) -> Any:
128
+ return self._http.request("POST", "/v1/rooms/private/snapshots/query", {"roomId": room_id, "snapshotId": snapshot_id})
129
+
130
+ def get_frozen_snapshot_download(self, room_id: str, snapshot_id: str) -> Any:
131
+ return self._http.request("POST", "/v1/rooms/private/snapshots/download", {"roomId": room_id, "snapshotId": snapshot_id})
132
+
133
+ def attach_courseware(self, room_id: str, **input: Any) -> Any:
134
+ return self._http.request("POST", "/v1/rooms/coursewares", {**input, "roomId": room_id})
135
+
136
+
96
137
  class WhiteboardClient:
97
138
  def __init__(self, http: HttpTransport, *, app_id: str, app_secret: str) -> None:
98
139
  self.auth = _Auth(app_id, app_secret)
99
140
  self.recordings = _Recordings(http)
100
141
  self.captures = _Captures(http)
101
142
  self.files = _Files(http)
143
+ self.rooms = _Rooms(http)
102
144
  self._http = http
103
145
 
104
146
  @property
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eduskit
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: Eduskit B-end server SDK for classroom and whiteboard APIs
5
5
  Author: Eduskit
6
6
  License-Expression: Apache-2.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "eduskit"
3
- version = "0.1.4"
3
+ version = "0.1.5"
4
4
  description = "Eduskit B-end server SDK for classroom and whiteboard APIs"
5
5
  requires-python = ">=3.9"
6
6
  readme = "README.md"
@@ -1,4 +1,5 @@
1
1
  import json
2
+ import base64
2
3
  import unittest
3
4
 
4
5
  from eduskit import Eduskit, EduskitError
@@ -22,6 +23,39 @@ def mock_fetch(body, status=200, headers=None):
22
23
 
23
24
 
24
25
  class EduskitTest(unittest.TestCase):
26
+ def test_private_room_server_routes(self):
27
+ fetch, calls = mock_fetch({"code": 0, "data": {"marker": "server-result"}})
28
+ sdk = Eduskit(whiteboard_client={"base_url": "http://wb.test", "app_id": "app_wb", "app_key": "wk", "app_secret": "ws"}, fetch=fetch)
29
+ rooms = sdk.whiteboard_client.rooms
30
+ operations = [
31
+ lambda: rooms.provision_private_room("room_a", "assignment_a"),
32
+ lambda: rooms.change_private_room_grant("room_a", userId="student_a", requestId="grant_a", expectedGeneration="9223372036854775806", action="grant", role="participant"),
33
+ lambda: rooms.get_private_room_access("room_a", "student_a"),
34
+ lambda: rooms.issue_private_room_token("room_a", userId="student_a", role="participant", expiresIn=600),
35
+ lambda: rooms.seal_private_room("room_a"),
36
+ lambda: rooms.create_frozen_snapshot("room_a", "snapshot_a"),
37
+ lambda: rooms.get_frozen_snapshot("room_a", "snapshot_a"),
38
+ lambda: rooms.get_frozen_snapshot_download("room_a", "snapshot_a"),
39
+ lambda: rooms.initialize_private_workspace("room_a", "assignment_a", None),
40
+ lambda: rooms.initialize_private_workspace("room_a", "assignment_a", "snapshot_a"),
41
+ lambda: rooms.get_private_workspace_initialization("room_a"),
42
+ lambda: rooms.schedule_private_room_writes("room_a", "window_a", "2026-10-02T00:00:00.000Z", "2026-10-02T00:10:00.000Z"),
43
+ ]
44
+ for operation in operations:
45
+ self.assertEqual(operation(), {"marker": "server-result"})
46
+ expected = ["", "/grants", "/access/query", "/token", "/seal", "/snapshots", "/snapshots/query", "/snapshots/download", "/initializations", "/initializations", "/initializations/query", "/write-window"]
47
+ self.assertEqual([call["url"] for call in calls], ["http://wb.test/v1/rooms/private" + suffix for suffix in expected])
48
+ for call in calls:
49
+ self.assertEqual(call["method"], "POST")
50
+ self.assertEqual(call["body"]["roomId"], "room_a")
51
+ self.assertEqual(call["headers"]["x-app-key"], "wk")
52
+ self.assertEqual(calls[1]["body"]["expectedGeneration"], "9223372036854775806")
53
+ self.assertNotIn("accessGeneration", calls[3]["body"])
54
+ self.assertEqual(calls[8]["body"], {"roomId": "room_a", "assignmentId": "assignment_a", "sourceSnapshotId": None})
55
+ self.assertEqual(calls[9]["body"]["sourceSnapshotId"], "snapshot_a")
56
+ self.assertEqual(calls[10]["body"], {"roomId": "room_a"})
57
+ self.assertEqual(calls[11]["body"], {"roomId":"room_a", "requestId":"window_a", "opensAt":"2026-10-02T00:00:00.000Z", "closesAt":"2026-10-02T00:10:00.000Z"})
58
+
25
59
  def test_classroom_register(self):
26
60
  fetch, calls = mock_fetch(
27
61
  {
@@ -56,6 +90,10 @@ class EduskitTest(unittest.TestCase):
56
90
  )
57
91
  self.assertEqual(token["appId"], "app_wb")
58
92
  self.assertEqual(len(token["token"].split(".")), 3)
93
+ segment = token["token"].split(".")[1]
94
+ claims = json.loads(base64.urlsafe_b64decode(segment + "=" * (-len(segment) % 4)))
95
+ self.assertIn("access_generation", claims)
96
+ self.assertIsNone(claims["access_generation"])
59
97
  self.assertEqual(calls, [])
60
98
 
61
99
  def test_error_envelope(self):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes