PyHiveLMS 1.0.0__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.
Files changed (55) hide show
  1. pyhive/__init__.py +13 -0
  2. pyhive/cli/__init__.py +0 -0
  3. pyhive/cli/main.py +30 -0
  4. pyhive/client.py +570 -0
  5. pyhive/src/__init__.py +0 -0
  6. pyhive/src/_generated_versions.py +3 -0
  7. pyhive/src/api_versions.py +6 -0
  8. pyhive/src/authenticated_hive_client.py +250 -0
  9. pyhive/src/types/__init__.py +0 -0
  10. pyhive/src/types/assignment.py +210 -0
  11. pyhive/src/types/assignment_response.py +205 -0
  12. pyhive/src/types/assignment_response_content.py +131 -0
  13. pyhive/src/types/autocheck_status.py +94 -0
  14. pyhive/src/types/class_.py +113 -0
  15. pyhive/src/types/common.py +56 -0
  16. pyhive/src/types/core_item.py +22 -0
  17. pyhive/src/types/enums/__init__.py +0 -0
  18. pyhive/src/types/enums/action_enum.py +18 -0
  19. pyhive/src/types/enums/assignment_response_type_enum.py +17 -0
  20. pyhive/src/types/enums/assignment_status_enum.py +17 -0
  21. pyhive/src/types/enums/class_type_enum.py +13 -0
  22. pyhive/src/types/enums/clearance_enum.py +16 -0
  23. pyhive/src/types/enums/event_type_enum.py +14 -0
  24. pyhive/src/types/enums/exercise_patbas_enum.py +15 -0
  25. pyhive/src/types/enums/exercise_preview_types.py +15 -0
  26. pyhive/src/types/enums/form_field_type_enum.py +15 -0
  27. pyhive/src/types/enums/gender_enum.py +14 -0
  28. pyhive/src/types/enums/help_response_type_enum.py +14 -0
  29. pyhive/src/types/enums/help_status_enum.py +13 -0
  30. pyhive/src/types/enums/help_type_enum.py +18 -0
  31. pyhive/src/types/enums/queue_rule_enum.py +15 -0
  32. pyhive/src/types/enums/status_enum.py +21 -0
  33. pyhive/src/types/enums/sync_status_enum.py +15 -0
  34. pyhive/src/types/enums/visibility_enum.py +14 -0
  35. pyhive/src/types/event.py +140 -0
  36. pyhive/src/types/event_attendees_type_0_item.py +69 -0
  37. pyhive/src/types/event_color.py +63 -0
  38. pyhive/src/types/exercise.py +216 -0
  39. pyhive/src/types/form_field.py +152 -0
  40. pyhive/src/types/help_.py +275 -0
  41. pyhive/src/types/help_response.py +113 -0
  42. pyhive/src/types/help_response_segel_nested.py +129 -0
  43. pyhive/src/types/module.py +141 -0
  44. pyhive/src/types/notification_nested.py +80 -0
  45. pyhive/src/types/program.py +180 -0
  46. pyhive/src/types/queue.py +150 -0
  47. pyhive/src/types/queue_item.py +88 -0
  48. pyhive/src/types/subject.py +156 -0
  49. pyhive/src/types/tag.py +62 -0
  50. pyhive/src/types/user.py +450 -0
  51. pyhive/types.py +23 -0
  52. pyhivelms-1.0.0.dist-info/METADATA +156 -0
  53. pyhivelms-1.0.0.dist-info/RECORD +55 -0
  54. pyhivelms-1.0.0.dist-info/WHEEL +4 -0
  55. pyhivelms-1.0.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,275 @@
1
+ """Model for student help requests (auto-generated).
2
+
3
+ This module defines the :class:`Help` model used to represent student
4
+ help requests and provides serialization helpers.
5
+ """
6
+
7
+ from collections.abc import Mapping
8
+ from typing import TYPE_CHECKING, Any, Self, TypeVar, Union, cast
9
+
10
+ from attr import field
11
+ from attrs import define
12
+ from .common import UNSET, Unset
13
+ from .enums.help_status_enum import HelpStatusEnum
14
+ from .enums.help_type_enum import HelpTypeEnum
15
+ from .enums.visibility_enum import VisibilityEnum
16
+ from .core_item import HiveCoreItem
17
+
18
+ if TYPE_CHECKING:
19
+ from ...client import HiveClient
20
+ from .user import User
21
+ from .exercise import Exercise
22
+ from .help_response_segel_nested import HelpResponseSegelNested
23
+ from .notification_nested import NotificationNested
24
+
25
+
26
+ T = TypeVar("T", bound="Help")
27
+
28
+
29
+ @define
30
+ class Help(HiveCoreItem):
31
+ """A student's help request.
32
+
33
+ Attributes:
34
+ id (int):
35
+ user (int):
36
+ checker (Union[None, int]):
37
+ checker_first_name (str):
38
+ checker_last_name (str):
39
+ is_subscribed (bool):
40
+ help_type (HelpTypeEnum):
41
+ * `Exercise` - Exercise
42
+ * `Medical` - Medical
43
+ * `Error` - Error
44
+ * `Music` - Music
45
+ * `Request` - Request
46
+ * `Other` - Other
47
+ * `Chat` - Chat
48
+ help_status (HelpStatusEnum):
49
+ * `Resolved` - Resolved
50
+ * `Open` - Open
51
+ for_exercise (Union['Exercise', None]):
52
+ responses (list['HelpResponseSegelNested']):
53
+ notifications (list['NotificationNested']):
54
+ title (Union[Unset, str]):
55
+ visibility (Union[Unset, VisibilityEnum]):
56
+ * `All Staff` - Allstaff
57
+ * `All Staff And Checkers` - Allstaffandcheckers
58
+ * `Author Only` - Authoronly
59
+
60
+ """
61
+
62
+ hive_client: "HiveClient"
63
+ id: int
64
+ user_id: int
65
+ _user: Union["User", None] = field(init=False, default=None)
66
+ checker_id: None | int
67
+ _checker: Union["User", None] = field(init=False, default=None)
68
+ checker_first_name: str
69
+ checker_last_name: str
70
+ is_subscribed: bool
71
+ help_type: HelpTypeEnum
72
+ help_status: HelpStatusEnum
73
+ for_exercise_id: int | None
74
+ _for_exercise: Union["Exercise", None] = field(init=False, default=None)
75
+ responses: list["HelpResponseSegelNested"]
76
+ notifications: list["NotificationNested"]
77
+ title: Unset | str = UNSET
78
+ visibility: Unset | VisibilityEnum = UNSET
79
+
80
+ def to_dict(self) -> dict[str, Any]: # pylint: disable=too-many-locals
81
+ """Serialize this Help instance to a plain dictionary.
82
+
83
+ Returns:
84
+ A JSON-serializable mapping representing this help request.
85
+ """
86
+
87
+ # Import locally to avoid circular imports at module import time.
88
+ # Keep the import local but silence pylint's import-outside-toplevel.
89
+ from .exercise import ( # pylint: disable=import-outside-toplevel
90
+ Exercise,
91
+ )
92
+
93
+ # Keep the attribute name `id` as generated; silence redefined-builtin warning.
94
+ id = self.id # pylint: disable=redefined-builtin
95
+
96
+ user = self.user
97
+
98
+ checker_id = self.checker_id
99
+
100
+ checker_first_name = self.checker_first_name
101
+
102
+ checker_last_name = self.checker_last_name
103
+
104
+ is_subscribed = self.is_subscribed
105
+
106
+ help_type = self.help_type.value
107
+
108
+ help_status = self.help_status.value
109
+
110
+ for_exercise: None | dict[str, Any]
111
+ for_exercise = (
112
+ self.for_exercise.to_dict()
113
+ if isinstance(self.for_exercise, Exercise)
114
+ else self.for_exercise
115
+ )
116
+
117
+ responses: list[dict[str, Any]] = []
118
+ for responses_item_data in self.responses:
119
+ responses_item = responses_item_data.to_dict()
120
+ responses.append(responses_item)
121
+
122
+ notifications: list[dict[str, Any]] = []
123
+ for notifications_item_data in self.notifications:
124
+ notifications_item = notifications_item_data.to_dict()
125
+ notifications.append(notifications_item)
126
+
127
+ title = self.title
128
+
129
+ visibility: Unset | str = UNSET
130
+ if not isinstance(self.visibility, Unset):
131
+ visibility = self.visibility.value
132
+
133
+ field_dict: dict[str, Any] = {}
134
+ field_dict.update(
135
+ {
136
+ "id": id,
137
+ "user": user,
138
+ "checker": checker_id,
139
+ "checker_first_name": checker_first_name,
140
+ "checker_last_name": checker_last_name,
141
+ "is_subscribed": is_subscribed,
142
+ "help_type": help_type,
143
+ "help_status": help_status,
144
+ "for_exercise": for_exercise,
145
+ "responses": responses,
146
+ "notifications": notifications,
147
+ },
148
+ )
149
+ if title is not UNSET:
150
+ field_dict["title"] = title
151
+ if visibility is not UNSET:
152
+ field_dict["visibility"] = visibility
153
+
154
+ return field_dict
155
+
156
+ @classmethod
157
+ def from_dict( # pylint: disable=too-many-locals
158
+ cls, src_dict: Mapping[str, Any], hive_client: "HiveClient"
159
+ ) -> Self:
160
+ """Deserialize a Help instance from a mapping.
161
+
162
+ Args:
163
+ src_dict: The source mapping (typically parsed JSON).
164
+ hive_client: The HiveClient used for lazy-loading related objects.
165
+
166
+ Returns:
167
+ A populated :class:`Help` instance.
168
+ """
169
+
170
+ # Local imports to avoid runtime import cycles; keep but silence pylint.
171
+ from .help_response_segel_nested import ( # pylint: disable=import-outside-toplevel
172
+ HelpResponseSegelNested,
173
+ )
174
+ from .notification_nested import ( # pylint: disable=import-outside-toplevel
175
+ NotificationNested,
176
+ )
177
+
178
+ d = dict(src_dict)
179
+ id = d.pop("id")
180
+
181
+ user_id = d.pop("user")
182
+
183
+ def _parse_checker(data: object) -> None | int:
184
+ if data is None:
185
+ return data
186
+ return cast("None | int", data)
187
+
188
+ checker_id = _parse_checker(d.pop("checker"))
189
+
190
+ checker_first_name = d.pop("checker_first_name")
191
+
192
+ checker_last_name = d.pop("checker_last_name")
193
+
194
+ is_subscribed = d.pop("is_subscribed")
195
+
196
+ help_type = HelpTypeEnum(d.pop("help_type"))
197
+
198
+ help_status = HelpStatusEnum(d.pop("help_status"))
199
+
200
+ def _parse_for_exercise(data: object) -> int | None:
201
+ if data is None:
202
+ return data
203
+ try:
204
+ if not isinstance(data, dict):
205
+ raise TypeError
206
+ return cast("int", data["id"])
207
+ except Exception: # pylint: disable=broad-except
208
+ # When the structure is unexpected, treat as missing.
209
+ return None
210
+
211
+ for_exercise_id = _parse_for_exercise(d.pop("for_exercise"))
212
+
213
+ responses = [
214
+ HelpResponseSegelNested.from_dict(
215
+ responses_item_data, hive_client=hive_client
216
+ )
217
+ for responses_item_data in d.pop("responses")
218
+ ]
219
+
220
+ notifications: list[NotificationNested] = [
221
+ NotificationNested.from_dict(
222
+ notifications_item_data, hive_client=hive_client
223
+ )
224
+ for notifications_item_data in d.pop("notifications")
225
+ ]
226
+
227
+ title = d.pop("title", UNSET)
228
+
229
+ _visibility = d.pop("visibility", UNSET)
230
+ visibility: Unset | VisibilityEnum
231
+ visibility = (
232
+ UNSET if isinstance(_visibility, Unset) else VisibilityEnum(_visibility)
233
+ )
234
+
235
+ return cls(
236
+ id=id,
237
+ user_id=user_id,
238
+ checker_id=checker_id,
239
+ checker_first_name=checker_first_name,
240
+ checker_last_name=checker_last_name,
241
+ is_subscribed=is_subscribed,
242
+ help_type=help_type,
243
+ help_status=help_status,
244
+ for_exercise_id=for_exercise_id,
245
+ responses=responses,
246
+ notifications=notifications,
247
+ title=title,
248
+ visibility=visibility,
249
+ hive_client=hive_client,
250
+ )
251
+
252
+ @property
253
+ def for_exercise(self) -> Union["Exercise", None]:
254
+ """Lazily load and return the related Exercise, if any.
255
+
256
+ Returns:
257
+ The resolved :class:`Exercise` instance or None when not set.
258
+ """
259
+ if self.for_exercise_id is None:
260
+ return None
261
+ if self._for_exercise is None:
262
+ self._for_exercise = self.hive_client.get_exercise(self.for_exercise_id)
263
+ return self._for_exercise
264
+
265
+ @property
266
+ def user(self) -> "User":
267
+ """User which opened this help request.
268
+
269
+ Returns:
270
+ User: The user instance.
271
+
272
+ """
273
+ if self._user is None:
274
+ self._user = self.hive_client.get_user(self.user_id)
275
+ return self._user
@@ -0,0 +1,113 @@
1
+ """Model definition for help responses in the Hive system.
2
+
3
+ Represents a reply to a help request, which may include resolution,
4
+ comments, attachments, and display preferences.
5
+ """
6
+
7
+ import datetime
8
+ from collections.abc import Mapping
9
+ from typing import TYPE_CHECKING, Any, Self, TypeVar, cast
10
+
11
+ from attrs import define
12
+ from attrs import field
13
+ from dateutil.parser import isoparse
14
+ from .common import UNSET, Unset
15
+ from .core_item import HiveCoreItem
16
+ from .enums.help_response_type_enum import HelpResponseTypeEnum
17
+
18
+ if TYPE_CHECKING:
19
+ from ...client import HiveClient
20
+ from .user import User
21
+
22
+ T = TypeVar("T", bound="HelpResponse")
23
+
24
+
25
+ @define
26
+ class HelpResponse(HiveCoreItem):
27
+ """A response to a help request.
28
+
29
+ Attributes:
30
+ id: Unique identifier for the response.
31
+ user: ID of the responding user.
32
+ date: Timestamp of the response.
33
+ response_type: Type of the response (e.g., Resolve, Open, Comment).
34
+ contents: Optional text content of the response.
35
+ file_name: Optional name of an attached file.
36
+ dear_student: Whether to include a "Dear student" greeting. Default is True.
37
+ hide_checker_name: If True, the name of the checker is hidden.
38
+ segel_only: If True, the response is visible only to staff.
39
+
40
+ """
41
+
42
+ hive_client: "HiveClient"
43
+ id: int
44
+ user_id: int
45
+ date: datetime.datetime
46
+ response_type: HelpResponseTypeEnum
47
+ contents: None | Unset | str = UNSET
48
+ file_name: Unset | str = UNSET
49
+ dear_student: Unset | bool = True
50
+ hide_checker_name: Unset | bool = UNSET
51
+ segel_only: Unset | bool = UNSET
52
+
53
+ # Lazy-loaded objects
54
+ _user: "User | None" = field(init=False, default=None)
55
+
56
+ @property
57
+ def user(self) -> "User":
58
+ """Returns the User object associated with this instance.
59
+
60
+ If the User object has not been retrieved yet,
61
+ it fetches the user using the hive_client and caches it for future calls.
62
+
63
+ Returns:
64
+ User: The user associated with this instance.
65
+
66
+ """
67
+ if self._user is None:
68
+ self._user = self.hive_client.get_user(self.user_id)
69
+ return self._user
70
+
71
+ def to_dict(self) -> dict[str, Any]:
72
+ contents = UNSET if isinstance(self.contents, Unset) else self.contents
73
+
74
+ field_dict: dict[str, Any] = {
75
+ "id": self.id,
76
+ "user": self.user_id,
77
+ "date": self.date.isoformat(),
78
+ "response_type": self.response_type.value,
79
+ }
80
+ if contents is not UNSET:
81
+ field_dict["contents"] = contents
82
+ if self.file_name is not UNSET:
83
+ field_dict["file_name"] = self.file_name
84
+ if self.dear_student is not UNSET:
85
+ field_dict["dear_student"] = self.dear_student
86
+ if self.hide_checker_name is not UNSET:
87
+ field_dict["hide_checker_name"] = self.hide_checker_name
88
+ if self.segel_only is not UNSET:
89
+ field_dict["segel_only"] = self.segel_only
90
+
91
+ return field_dict
92
+
93
+ @classmethod
94
+ def from_dict(cls, src_dict: Mapping[str, Any], hive_client: "HiveClient") -> Self:
95
+ d = dict(src_dict)
96
+
97
+ def _parse_optional_str(data: object) -> None | Unset | str:
98
+ if data is None or isinstance(data, Unset):
99
+ return data
100
+ return cast("str", data)
101
+
102
+ return cls(
103
+ hive_client=hive_client,
104
+ id=d.pop("id"),
105
+ user_id=d.pop("user"),
106
+ date=isoparse(d.pop("date")),
107
+ response_type=HelpResponseTypeEnum(d.pop("response_type")),
108
+ contents=_parse_optional_str(d.pop("contents", UNSET)),
109
+ file_name=d.pop("file_name", UNSET),
110
+ dear_student=d.pop("dear_student", UNSET),
111
+ hide_checker_name=d.pop("hide_checker_name", UNSET),
112
+ segel_only=d.pop("segel_only", UNSET),
113
+ )
@@ -0,0 +1,129 @@
1
+ """ "This module contains the HelpResponseSegelNested class."""
2
+
3
+ import datetime
4
+ from collections.abc import Mapping
5
+ from typing import TYPE_CHECKING, Any, Self, TypeVar, cast
6
+
7
+ from attrs import define
8
+ from dateutil.parser import isoparse
9
+ from .common import UNSET, Unset
10
+ from .enums.help_response_type_enum import HelpResponseTypeEnum
11
+ from .core_item import HiveCoreItem
12
+
13
+ if TYPE_CHECKING:
14
+ from ...client import HiveClient
15
+
16
+ T = TypeVar("T", bound="HelpResponseSegelNested")
17
+
18
+
19
+ @define
20
+ class HelpResponseSegelNested(HiveCoreItem):
21
+ """Attributes:
22
+ id (int):
23
+ user (int):
24
+ date (datetime.datetime):
25
+ response_type (HelpResponseTypeEnum):
26
+ * `Resolve` - Resolve
27
+ * `Open` - Open
28
+ * `Comment` - Comment
29
+ contents (Union[None, Unset, str]):
30
+ file_name (Union[Unset, str]):
31
+ dear_student (Union[Unset, bool]): Default: True.
32
+ hide_checker_name (Union[Unset, bool]):
33
+ segel_only (Union[Unset, bool]):
34
+
35
+ """
36
+
37
+ hive_client: "HiveClient"
38
+ id: int
39
+ user: int
40
+ date: datetime.datetime
41
+ response_type: HelpResponseTypeEnum
42
+ contents: None | Unset | str = UNSET
43
+ file_name: Unset | str = UNSET
44
+ dear_student: Unset | bool = True
45
+ hide_checker_name: Unset | bool = UNSET
46
+ segel_only: Unset | bool = UNSET
47
+
48
+ def to_dict(self) -> dict[str, Any]:
49
+ id = self.id
50
+
51
+ user = self.user
52
+
53
+ date = self.date.isoformat()
54
+
55
+ response_type = self.response_type.value
56
+
57
+ contents: None | Unset | str
58
+ contents = UNSET if isinstance(self.contents, Unset) else self.contents
59
+
60
+ file_name = self.file_name
61
+
62
+ dear_student = self.dear_student
63
+
64
+ hide_checker_name = self.hide_checker_name
65
+
66
+ segel_only = self.segel_only
67
+
68
+ field_dict: dict[str, Any] = {}
69
+ field_dict.update(
70
+ {
71
+ "id": id,
72
+ "user": user,
73
+ "date": date,
74
+ "response_type": response_type,
75
+ },
76
+ )
77
+ if contents is not UNSET:
78
+ field_dict["contents"] = contents
79
+ if file_name is not UNSET:
80
+ field_dict["file_name"] = file_name
81
+ if dear_student is not UNSET:
82
+ field_dict["dear_student"] = dear_student
83
+ if hide_checker_name is not UNSET:
84
+ field_dict["hide_checker_name"] = hide_checker_name
85
+ if segel_only is not UNSET:
86
+ field_dict["segel_only"] = segel_only
87
+
88
+ return field_dict
89
+
90
+ @classmethod
91
+ def from_dict(cls, src_dict: Mapping[str, Any], hive_client: "HiveClient") -> Self:
92
+ d = dict(src_dict)
93
+ id = d.pop("id")
94
+
95
+ user = d.pop("user")
96
+
97
+ date = isoparse(d.pop("date"))
98
+
99
+ response_type = HelpResponseTypeEnum(d.pop("response_type"))
100
+
101
+ def _parse_contents(data: object) -> None | Unset | str:
102
+ if data is None:
103
+ return data
104
+ if isinstance(data, Unset):
105
+ return data
106
+ return cast("None | Unset | str", data)
107
+
108
+ contents = _parse_contents(d.pop("contents", UNSET))
109
+
110
+ file_name = d.pop("file_name", UNSET)
111
+
112
+ dear_student = d.pop("dear_student", UNSET)
113
+
114
+ hide_checker_name = d.pop("hide_checker_name", UNSET)
115
+
116
+ segel_only = d.pop("segel_only", UNSET)
117
+
118
+ return cls(
119
+ id=id,
120
+ user=user,
121
+ date=date,
122
+ response_type=response_type,
123
+ contents=contents,
124
+ file_name=file_name,
125
+ dear_student=dear_student,
126
+ hide_checker_name=hide_checker_name,
127
+ segel_only=segel_only,
128
+ hive_client=hive_client,
129
+ )
@@ -0,0 +1,141 @@
1
+ """Module model definition for the Hive system.
2
+
3
+ Represents a logical course module within a subject, supporting serialization,
4
+ lazy loading of parent subject, and retrieval of exercises.
5
+ """
6
+
7
+ from collections.abc import Generator, Mapping
8
+ from typing import TYPE_CHECKING, Any, Self, TypeVar, cast
9
+
10
+ from attrs import define
11
+ from attrs import field
12
+ from .enums.sync_status_enum import SyncStatusEnum
13
+ from .exercise import Exercise
14
+ from .program import HiveCoreItem
15
+
16
+ if TYPE_CHECKING:
17
+ from ...client import HiveClient
18
+ from .subject import Subject
19
+
20
+ T = TypeVar("T", bound="Module")
21
+
22
+
23
+ @define
24
+ class Module(HiveCoreItem):
25
+ """Course Subject Module.
26
+
27
+ Attributes:
28
+ id: Unique identifier.
29
+ name: Name of the module.
30
+ parent_subject_id: ID of the parent subject.
31
+ order: Order of display within the subject.
32
+ sync_status: Synchronization status.
33
+ sync_message: Optional error or status message.
34
+ parent_program_name: Name of the program the subject belongs to.
35
+ parent_subject_name: Name of the parent subject.
36
+ parent_subject_symbol: Symbol of the parent subject.
37
+ segel_path: Network path accessible to staff.
38
+
39
+ """
40
+
41
+ hive_client: "HiveClient"
42
+ id: int
43
+ name: str
44
+ parent_subject_id: int
45
+ order: str
46
+ sync_status: SyncStatusEnum
47
+ sync_message: None | str
48
+ parent_program_name: str
49
+ parent_subject_name: str
50
+ parent_subject_symbol: str
51
+ segel_path: str
52
+
53
+ _parent_subject: "Subject | None" = field(init=False, default=None)
54
+
55
+ @property
56
+ def parent_subject(self) -> "Subject":
57
+ """Lazily load and return the parent subject."""
58
+ if self._parent_subject is None:
59
+ self._parent_subject = self.hive_client.get_subject(self.parent_subject_id)
60
+ return self._parent_subject
61
+
62
+ def to_dict(self) -> dict[str, Any]:
63
+ """Serialize the module to a dictionary."""
64
+ return {
65
+ "id": self.id,
66
+ "name": self.name,
67
+ "parent_subject": self.parent_subject_id,
68
+ "order": self.order,
69
+ "sync_status": self.sync_status.value,
70
+ "sync_message": self.sync_message,
71
+ "parent_program_name": self.parent_program_name,
72
+ "parent_subject_name": self.parent_subject_name,
73
+ "parent_subject_symbol": self.parent_subject_symbol,
74
+ "segel_path": self.segel_path,
75
+ }
76
+
77
+ @classmethod
78
+ def from_dict(cls, src_dict: Mapping[str, Any], hive_client: "HiveClient") -> Self:
79
+ """Deserialize a module from a dictionary."""
80
+ d = dict(src_dict)
81
+ return cls(
82
+ hive_client=hive_client,
83
+ id=d["id"],
84
+ name=d["name"],
85
+ parent_subject_id=d["parent_subject"],
86
+ order=d["order"],
87
+ sync_status=SyncStatusEnum(d["sync_status"]),
88
+ sync_message=cast("None | str", d["sync_message"]),
89
+ parent_program_name=d["parent_program_name"],
90
+ parent_subject_name=d["parent_subject_name"],
91
+ parent_subject_symbol=d["parent_subject_symbol"],
92
+ segel_path=d["segel_path"],
93
+ )
94
+
95
+ def __eq__(self, value: object) -> bool:
96
+ if not isinstance(value, Module):
97
+ return False
98
+ return self.id == value.id and self.parent_subject == value.parent_subject
99
+
100
+ def __lt__(self, value: object) -> bool:
101
+ if not isinstance(value, Module):
102
+ return NotImplemented
103
+ return self.order < value.order
104
+
105
+ def get_exercises(self) -> Generator[Exercise]:
106
+ """Fetch all exercises within this module."""
107
+ return self.hive_client.get_exercises(parent_module__id=self.id)
108
+
109
+ def get_exercise(self, exercise_name: str) -> Exercise:
110
+ """Fetch a specific exercise by name within this module."""
111
+ exercises = list(
112
+ self.hive_client.get_exercises(
113
+ parent_module__id=self.id,
114
+ exercise_name=exercise_name,
115
+ )
116
+ )
117
+
118
+ if len(exercises) == 0:
119
+ raise ValueError(
120
+ f"Exercise '{exercise_name}' not found in module '{self.name}'"
121
+ )
122
+ if len(exercises) > 1:
123
+ raise ValueError(
124
+ f"Multiple exercises named '{exercise_name}' found in module '{self.name}'"
125
+ )
126
+ return exercises[0]
127
+
128
+ def __iter__(self) -> Generator["Exercise", None, None]:
129
+ """Allow iteration over this Module to yield its exercises."""
130
+ yield from self.get_exercises()
131
+
132
+ def __hash__(self) -> int:
133
+ return hash(
134
+ (
135
+ self.id,
136
+ self.parent_subject_id,
137
+ )
138
+ )
139
+
140
+
141
+ ModuleLike = TypeVar("ModuleLike", Module, int)