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,62 @@
1
+ """Module defining the Tag class for Hive tags."""
2
+
3
+ from collections.abc import Mapping
4
+ from typing import TYPE_CHECKING, Any, Self, TypeVar
5
+
6
+ from attrs import define
7
+ from .core_item import HiveCoreItem
8
+
9
+ if TYPE_CHECKING:
10
+ from ...client import HiveClient
11
+
12
+ T = TypeVar("T", bound="Tag")
13
+
14
+
15
+ @define
16
+ class Tag(HiveCoreItem):
17
+ """Attributes:
18
+ id (int):
19
+ name (str):
20
+ color (str):
21
+
22
+ """
23
+
24
+ hive_client: "HiveClient"
25
+ id: int
26
+ name: str
27
+ color: str
28
+
29
+ def to_dict(self) -> dict[str, Any]:
30
+ id = self.id
31
+
32
+ name = self.name
33
+
34
+ color = self.color
35
+
36
+ field_dict: dict[str, Any] = {}
37
+ field_dict.update(
38
+ {
39
+ "id": id,
40
+ "name": name,
41
+ "color": color,
42
+ },
43
+ )
44
+
45
+ return field_dict
46
+
47
+ @classmethod
48
+ def from_dict(cls, src_dict: Mapping[str, Any], hive_client: "HiveClient") -> Self:
49
+ d = dict(src_dict)
50
+ id = d.pop("id")
51
+
52
+ name = d.pop("name")
53
+
54
+ color = d.pop("color")
55
+
56
+ tag = cls(
57
+ id=id,
58
+ name=name,
59
+ color=color,
60
+ hive_client=hive_client,
61
+ )
62
+ return tag
@@ -0,0 +1,450 @@
1
+ """Hive management course user type."""
2
+
3
+ import datetime
4
+ from collections.abc import Mapping
5
+ from typing import TYPE_CHECKING, Any, Self, TypeVar, cast, Generator
6
+
7
+ from attrs import field, define
8
+ from dateutil.parser import isoparse
9
+ from .common import UNSET, Unset
10
+ from .core_item import HiveCoreItem
11
+ from .enums.clearance_enum import ClearanceEnum
12
+ from .enums.gender_enum import GenderEnum
13
+ from .enums.status_enum import StatusEnum
14
+
15
+ if TYPE_CHECKING:
16
+ from ...client import HiveClient
17
+ from .assignment import Assignment
18
+ from .class_ import Class
19
+ from .program import Program
20
+ from .queue import Queue
21
+
22
+ T = TypeVar("T", bound="User")
23
+
24
+
25
+ @define
26
+ class User(HiveCoreItem): # pylint: disable=too-many-instance-attributes
27
+ """Hive management course user.
28
+
29
+ Attributes:
30
+ id (int):
31
+ display_name (str):
32
+ clearance (ClearanceEnum):
33
+ * `1` - Hanich
34
+ * `2` - Checker
35
+ * `3` - Segel
36
+ * `5` - Admin
37
+ gender (GenderEnum):
38
+ * `Male` - Male
39
+ * `Female` - Female
40
+ * `NonBinary` - Nonbinary
41
+ current_assignment (Union[None, int]):
42
+ current_assignment_options (list[int]):
43
+ mentee_ids (list[int]):
44
+ username (str): Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.
45
+ status (StatusEnum):
46
+ * `Present` - Present
47
+ * `Raised Hand` - Raisedhand
48
+ * `Toilet Request` - Toiletrequest
49
+ * `Toilet` - Toilet
50
+ * `Personal Talk` - Personaltalk
51
+ * `Work Talk` - Worktalk
52
+ * `Medical` - Medical
53
+ * `Prayer` - Prayer
54
+ * `Room` - Room
55
+ * `Home` - Home
56
+ status_date (datetime.datetime):
57
+ avatar_filename (Union[Unset, str]):
58
+ number (Union[None, Unset, int]):
59
+ program (Union[None, Unset, int]):
60
+ checkers_brief (Union[Unset, str]):
61
+ mentor (Union[None, Unset, int]):
62
+ classes (Union[Unset, list[int]]):
63
+ first_name (Union[Unset, str]):
64
+ last_name (Union[Unset, str]):
65
+ queue (Union[None, Unset, int]):
66
+ disable_queue (Union[Unset, bool]):
67
+ user_queue (Union[None, Unset, int]):
68
+ disable_user_queue (Union[Unset, bool]):
69
+ override_queue (Union[None, Unset, int]):
70
+ confirmed (Union[Unset, bool]):
71
+ teacher (Union[Unset, bool]):
72
+ hostname (Union[Unset, str]):
73
+
74
+ """
75
+
76
+ hive_client: "HiveClient"
77
+ id: int
78
+ display_name: str
79
+ clearance: ClearanceEnum
80
+ gender: GenderEnum
81
+ current_assignment_id: None | int
82
+ _current_assignment: "Assignment | None" = field(init=False, default=None)
83
+ current_assignment_options: list[int]
84
+ mentee_ids: list[int]
85
+ _mentees: "list[User] | None" = field(init=False, default=None)
86
+ username: str
87
+ status: StatusEnum
88
+ status_date: datetime.datetime
89
+ avatar_filename: Unset | str = UNSET
90
+ number: None | Unset | int = UNSET
91
+ program_id: None | Unset | int = UNSET
92
+ _program: "Program | None" = field(init=False, default=None)
93
+ checkers_brief: Unset | str = UNSET
94
+ mentor_id: None | Unset | int = UNSET
95
+ _mentor: "User | None" = field(init=False, default=None)
96
+ class_ids: Unset | list[int] = UNSET
97
+ _classes: "list[Class] | None" = field(init=False, default=None)
98
+ first_name: Unset | str = UNSET
99
+ last_name: Unset | str = UNSET
100
+ queue_id: None | Unset | int = UNSET
101
+ _queue: "Queue | None" = field(init=False, default=None)
102
+ disable_queue: Unset | bool = UNSET
103
+ user_queue_id: None | Unset | int = UNSET
104
+ _user_queue: "Queue | None" = field(init=False, default=None)
105
+ disable_user_queue: Unset | bool = UNSET
106
+ override_queue_id: None | Unset | int = UNSET
107
+ _override_queue: "Queue | None" = field(init=False, default=None)
108
+ confirmed: Unset | bool = UNSET
109
+ teacher: Unset | bool = UNSET
110
+ hostname: Unset | str = UNSET
111
+
112
+ def to_dict( # pylint: disable=too-many-locals, too-many-statements, too-many-branches
113
+ self,
114
+ ) -> dict[str, Any]:
115
+ id = self.id
116
+
117
+ display_name = self.display_name
118
+
119
+ clearance = self.clearance.value
120
+
121
+ gender = self.gender.value
122
+
123
+ current_assignment_id: None | int
124
+ current_assignment_id = self.current_assignment_id
125
+
126
+ current_assignment_options = self.current_assignment_options
127
+
128
+ mentee_ids = self.mentee_ids
129
+
130
+ username = self.username
131
+
132
+ status = self.status.value
133
+
134
+ status_date = self.status_date.isoformat()
135
+
136
+ avatar_filename = self.avatar_filename
137
+
138
+ number: None | Unset | int
139
+ number = UNSET if isinstance(self.number, Unset) else self.number
140
+
141
+ program_id: None | Unset | int
142
+ program_id = UNSET if isinstance(self.program_id, Unset) else self.program_id
143
+
144
+ checkers_brief = self.checkers_brief
145
+
146
+ mentor_id: None | Unset | int
147
+ mentor_id = UNSET if isinstance(self.mentor_id, Unset) else self.mentor_id
148
+
149
+ class_ids: Unset | list[int] = UNSET
150
+ if not isinstance(self.class_ids, Unset):
151
+ class_ids = self.class_ids
152
+
153
+ first_name = self.first_name
154
+
155
+ last_name = self.last_name
156
+
157
+ queue_id: None | Unset | int
158
+ queue_id = UNSET if isinstance(self.queue_id, Unset) else self.queue_id
159
+
160
+ disable_queue = self.disable_queue
161
+
162
+ user_queue_id: None | Unset | int
163
+ user_queue_id = (
164
+ UNSET if isinstance(self.user_queue_id, Unset) else self.user_queue_id
165
+ )
166
+
167
+ disable_user_queue = self.disable_user_queue
168
+
169
+ override_queue_id: None | Unset | int
170
+ override_queue_id = (
171
+ UNSET
172
+ if isinstance(self.override_queue_id, Unset)
173
+ else self.override_queue_id
174
+ )
175
+
176
+ confirmed = self.confirmed
177
+
178
+ teacher = self.teacher
179
+
180
+ hostname = self.hostname
181
+
182
+ field_dict: dict[str, Any] = {}
183
+ field_dict.update(
184
+ {
185
+ "id": id,
186
+ "display_name": display_name,
187
+ "clearance": clearance,
188
+ "gender": gender,
189
+ "current_assignment": current_assignment_id,
190
+ "current_assignment_options": current_assignment_options,
191
+ "mentees": mentee_ids,
192
+ "username": username,
193
+ "status": status,
194
+ "status_date": status_date,
195
+ },
196
+ )
197
+ if avatar_filename is not UNSET:
198
+ field_dict["avatar_filename"] = avatar_filename
199
+ if number is not UNSET:
200
+ field_dict["number"] = number
201
+ if program_id is not UNSET:
202
+ field_dict["program"] = program_id
203
+ if checkers_brief is not UNSET:
204
+ field_dict["checkers_brief"] = checkers_brief
205
+ if mentor_id is not UNSET:
206
+ field_dict["mentor"] = mentor_id
207
+ if class_ids is not UNSET:
208
+ field_dict["classes"] = class_ids
209
+ if first_name is not UNSET:
210
+ field_dict["first_name"] = first_name
211
+ if last_name is not UNSET:
212
+ field_dict["last_name"] = last_name
213
+ if queue_id is not UNSET:
214
+ field_dict["queue"] = queue_id
215
+ if disable_queue is not UNSET:
216
+ field_dict["disable_queue"] = disable_queue
217
+ if user_queue_id is not UNSET:
218
+ field_dict["user_queue"] = user_queue_id
219
+ if disable_user_queue is not UNSET:
220
+ field_dict["disable_user_queue"] = disable_user_queue
221
+ if override_queue_id is not UNSET:
222
+ field_dict["override_queue"] = override_queue_id
223
+ if confirmed is not UNSET:
224
+ field_dict["confirmed"] = confirmed
225
+ if teacher is not UNSET:
226
+ field_dict["teacher"] = teacher
227
+ if hostname is not UNSET:
228
+ field_dict["hostname"] = hostname
229
+
230
+ return field_dict
231
+
232
+ @classmethod
233
+ def from_dict( # pylint: disable=too-many-locals
234
+ cls,
235
+ src_dict: Mapping[str, Any],
236
+ hive_client: "HiveClient",
237
+ ) -> Self:
238
+ """Deserialize a User instance from a mapping."""
239
+ d = dict(src_dict)
240
+ id = d.pop("id")
241
+
242
+ display_name = d.pop("display_name")
243
+
244
+ clearance = ClearanceEnum(d.pop("clearance"))
245
+
246
+ gender = GenderEnum(d.pop("gender"))
247
+
248
+ def _parse_current_assignment(data: object) -> None | int:
249
+ if data is None:
250
+ return data
251
+ return cast("None | int", data)
252
+
253
+ current_assignment = _parse_current_assignment(d.pop("current_assignment"))
254
+
255
+ current_assignment_options = cast(
256
+ "list[int]", d.pop("current_assignment_options")
257
+ )
258
+
259
+ mentee_ids = cast("list[int]", d.pop("mentees"))
260
+
261
+ username = d.pop("username")
262
+
263
+ status = StatusEnum(d.pop("status"))
264
+
265
+ status_date = isoparse(d.pop("status_date"))
266
+
267
+ avatar_filename = d.pop("avatar_filename", UNSET)
268
+
269
+ def _parse_number(data: object) -> None | Unset | int:
270
+ if data is None:
271
+ return data
272
+ if isinstance(data, Unset):
273
+ return data
274
+ return cast("None | Unset | int", data)
275
+
276
+ number = _parse_number(d.pop("number", UNSET))
277
+
278
+ def _parse_program(data: object) -> None | Unset | int:
279
+ if data is None:
280
+ return data
281
+ if isinstance(data, Unset):
282
+ return data
283
+ return cast("None | Unset | int", data)
284
+
285
+ program = _parse_program(d.pop("program", UNSET))
286
+
287
+ checkers_brief = d.pop("checkers_brief", UNSET)
288
+
289
+ def _parse_mentor(data: object) -> None | Unset | int:
290
+ if data is None:
291
+ return data
292
+ if isinstance(data, Unset):
293
+ return data
294
+ return cast("None | Unset | int", data)
295
+
296
+ mentor = _parse_mentor(d.pop("mentor", UNSET))
297
+
298
+ classes = cast("list[int]", d.pop("classes", UNSET))
299
+
300
+ first_name = d.pop("first_name", UNSET)
301
+
302
+ last_name = d.pop("last_name", UNSET)
303
+
304
+ def _parse_queue(data: object) -> None | Unset | int:
305
+ if data is None:
306
+ return data
307
+ if isinstance(data, Unset):
308
+ return data
309
+ return cast("None | Unset | int", data)
310
+
311
+ queue = _parse_queue(d.pop("queue", UNSET))
312
+
313
+ disable_queue = d.pop("disable_queue", UNSET)
314
+
315
+ def _parse_user_queue(data: object) -> None | Unset | int:
316
+ if data is None:
317
+ return data
318
+ if isinstance(data, Unset):
319
+ return data
320
+ return cast("None | Unset | int", data)
321
+
322
+ user_queue = _parse_user_queue(d.pop("user_queue", UNSET))
323
+
324
+ disable_user_queue = d.pop("disable_user_queue", UNSET)
325
+
326
+ def _parse_override_queue(data: object) -> None | Unset | int:
327
+ if data is None:
328
+ return data
329
+ if isinstance(data, Unset):
330
+ return data
331
+ return cast("None | Unset | int", data)
332
+
333
+ override_queue = _parse_override_queue(d.pop("override_queue", UNSET))
334
+
335
+ confirmed = d.pop("confirmed", UNSET)
336
+
337
+ teacher = d.pop("teacher", UNSET)
338
+
339
+ hostname = d.pop("hostname", UNSET)
340
+
341
+ return cls(
342
+ id=id,
343
+ display_name=display_name,
344
+ clearance=clearance,
345
+ gender=gender,
346
+ current_assignment_id=current_assignment,
347
+ current_assignment_options=current_assignment_options,
348
+ mentee_ids=mentee_ids,
349
+ username=username,
350
+ status=status,
351
+ status_date=status_date,
352
+ avatar_filename=avatar_filename,
353
+ number=number,
354
+ program_id=program,
355
+ checkers_brief=checkers_brief,
356
+ mentor_id=mentor,
357
+ class_ids=classes,
358
+ first_name=first_name,
359
+ last_name=last_name,
360
+ queue_id=queue,
361
+ disable_queue=disable_queue,
362
+ user_queue_id=user_queue,
363
+ disable_user_queue=disable_user_queue,
364
+ override_queue_id=override_queue,
365
+ confirmed=confirmed,
366
+ teacher=teacher,
367
+ hostname=hostname,
368
+ hive_client=hive_client,
369
+ )
370
+
371
+ @property
372
+ def program(self) -> "Program | None":
373
+ """The program this user is in."""
374
+ if not isinstance(self.program_id, int):
375
+ return None
376
+ if self._program is None:
377
+ self._program = self.hive_client.get_program(self.program_id)
378
+ return self._program
379
+
380
+ @property
381
+ def mentees(self) -> list["User"]:
382
+ """The mentees of this user."""
383
+ if self._mentees is None:
384
+ self._mentees = list(
385
+ self.hive_client.get_users(user__id__in=self.mentee_ids)
386
+ )
387
+ return self._mentees
388
+
389
+ @property
390
+ def mentor(self) -> "User | None":
391
+ """The mentor of this user."""
392
+ if not isinstance(self.mentor_id, int):
393
+ return None
394
+ if self._mentor is None:
395
+ self._mentor = self.hive_client.get_user(self.mentor_id)
396
+ return self._mentor
397
+
398
+ @property
399
+ def classes(self) -> list["Class"]:
400
+ """The classes this user is in."""
401
+ if self._classes is None:
402
+ if isinstance(self.class_ids, Unset):
403
+ self._classes = []
404
+ else:
405
+ self._classes = list(
406
+ self.hive_client.get_classes(id__in=self.class_ids)
407
+ )
408
+ return self._classes
409
+
410
+ @property
411
+ def queue(self) -> "Queue | None":
412
+ """The queue this user is in."""
413
+ if not isinstance(self.queue_id, int):
414
+ return None
415
+ if self._queue is None:
416
+ self._queue = self.hive_client.get_queue(self.queue_id)
417
+ return self._queue
418
+
419
+ @property
420
+ def user_queue(self) -> "Queue | None":
421
+ """The user queue this user is in."""
422
+ if not isinstance(self.user_queue_id, int):
423
+ return None
424
+ if self._user_queue is None:
425
+ self._user_queue = self.hive_client.get_queue(self.user_queue_id)
426
+ return self._user_queue
427
+
428
+ @property
429
+ def override_queue(self) -> "Queue | None":
430
+ """The override queue this user is in."""
431
+ if not isinstance(self.override_queue_id, int):
432
+ return None
433
+ if self._override_queue is None:
434
+ self._override_queue = self.hive_client.get_queue(self.override_queue_id)
435
+ return self._override_queue
436
+
437
+ @property
438
+ def current_assignment(self) -> "Assignment | None":
439
+ """The current assignment of this user."""
440
+ if not isinstance(self.current_assignment_id, int):
441
+ return None
442
+ if self._current_assignment is None:
443
+ self._current_assignment = self.hive_client.get_assignment(
444
+ self.current_assignment_id
445
+ )
446
+ return self._current_assignment
447
+
448
+ def get_assignments(self) -> Generator["Assignment", None, None]:
449
+ """Get all assignments for this user."""
450
+ return self.hive_client.get_assignments(user__id__in=[self.id])
pyhive/types.py ADDED
@@ -0,0 +1,23 @@
1
+ """Type definitions for PyHiveLMS."""
2
+
3
+ from __future__ import annotations
4
+
5
+ # Import the implementation from the `pyhive` package (implementation
6
+ # lives there) and expose the client at package level.
7
+ from pyhive.src.types.assignment import Assignment # re-export
8
+ from pyhive.src.types.assignment_response import AssignmentResponse # re-export
9
+ from pyhive.src.types.user import User # re-export
10
+ from pyhive.src.types.module import Module # re-export
11
+ from pyhive.src.types.exercise import Exercise # re-export
12
+ from pyhive.src.types.class_ import Class
13
+ from pyhive.src.types.form_field import FormField
14
+
15
+ __all__ = [
16
+ "Assignment",
17
+ "AssignmentResponse",
18
+ "User",
19
+ "Module",
20
+ "Exercise",
21
+ "Class",
22
+ "FormField",
23
+ ]
@@ -0,0 +1,156 @@
1
+ Metadata-Version: 2.4
2
+ Name: PyHiveLMS
3
+ Version: 1.0.0
4
+ Summary: Python bindings for Hive
5
+ Author-email: "Michael K. Steinberg" <m.kuper.steinberg@gmail.com>
6
+ Requires-Python: >=3.11.0
7
+ Requires-Dist: attrs>=25.4.0
8
+ Requires-Dist: httpx
9
+ Requires-Dist: python-dateutil>=2.9.0
10
+ Requires-Dist: typer==0.20.0
11
+ Description-Content-Type: text/markdown
12
+
13
+ ## PyHive — a minimal Hive API client for Python
14
+
15
+ PyHive (package: `PyHiveLMS`) is a small, synchronous Python client for the Hive API. It provides:
16
+
17
+ - A simple `HiveClient` you use as a context manager to handle authentication and the HTTP session.
18
+ - Generator-based list endpoints for memory-efficient iteration of large result sets.
19
+
20
+ ### Supported Hive Versions
21
+ <!-- SUPPORTED_API_VERSIONS_START -->
22
+ - `5.1.2`
23
+ - `6.2.0`
24
+ <!-- SUPPORTED_API_VERSIONS_END -->
25
+
26
+ ## Install
27
+
28
+ Install from PyPI. It's recommended to use a virtual environment.
29
+
30
+ Using pip (PyPI):
31
+
32
+ ```pwsh
33
+ pip install PyHiveLMS
34
+ ```
35
+
36
+
37
+ ## Quickstart — connect and list resources
38
+
39
+ The primary entry point is `HiveClient`. It accepts your Hive username, password and the base URL for your Hive instance. Use it as a context manager to ensure the underlying HTTP session is closed cleanly.
40
+
41
+ Example — list programs and print name/ID:
42
+
43
+ ```python
44
+ from pyhive import HiveClient
45
+
46
+ USERNAME = "Mentor123"
47
+ PASSWORD = "Password1"
48
+ HIVE_URL = "https://hive.org"
49
+
50
+ with HiveClient(USERNAME, PASSWORD, HIVE_URL) as client:
51
+ for program in client.get_programs():
52
+ print(program.id, program.name)
53
+ ```
54
+
55
+ Example — fetch a program, its subjects and modules:
56
+
57
+ ```python
58
+ from pyhive import HiveClient
59
+
60
+ with HiveClient(USERNAME, PASSWORD, HIVE_URL) as client:
61
+ program = client.get_program(42)
62
+ print("Program:", program.name)
63
+
64
+ # list subjects for the same program
65
+ subjects = list(client.get_subjects(parent_program__id__in=[program.id]))
66
+ for subject in subjects:
67
+ print(" -", subject.id, subject.name)
68
+
69
+ # modules for the first subject
70
+ if subjects:
71
+ for module in client.get_modules(parent_subject=subjects[0]):
72
+ print(" *", module.id, module.name)
73
+ ```
74
+
75
+ Example — find exercises in a module and read their form fields:
76
+
77
+ ```python
78
+ with HiveClient(USERNAME, PASSWORD, HIVE_URL) as client:
79
+ # you can pass ids or model objects to filter helpers
80
+ module = client.get_module(123)
81
+ for exercise in client.get_exercises(parent_module=module):
82
+ print(exercise.id, exercise.name)
83
+ for field in client.get_exercise_fields(exercise):
84
+ print(" field:", field.id, field.label)
85
+ ```
86
+
87
+ Example — list assignments for a user and read responses:
88
+
89
+ ```python
90
+ with HiveClient(USERNAME, PASSWORD, HIVE_URL) as client:
91
+ assignments = client.get_assignments(user__id__in=[55])
92
+ for a in assignments:
93
+ print("Assignment:", a.id, a.exercise_name)
94
+ for resp in client.get_assignment_responses(a):
95
+ print(" response:", resp.id, resp.submitted_by)
96
+ ```
97
+
98
+ ## Filtering and convenience
99
+
100
+ - List endpoints (`get_programs`, `get_subjects`, `get_modules`, `get_exercises`, `get_assignments`, `get_users`, etc.) accept filter keyword arguments that are forwarded to the API. Use `id__in`, `parent_program__id__in`, `queue__id`, and the other documented kwargs to restrict results.
101
+ - Many methods accept either an integer id or a model instance. For example `client.get_exercise_fields(exercise_id_or_model)` accepts either.
102
+
103
+ ## Error handling
104
+
105
+ Network and HTTP errors are surfaced from the underlying `httpx` client. Typical patterns:
106
+
107
+ ```python
108
+ from httpx import HTTPError
109
+
110
+ try:
111
+ with HiveClient(USERNAME, PASSWORD, HIVE_URL) as client:
112
+ programs = list(client.get_programs())
113
+ except HTTPError as exc:
114
+ print("Network/HTTP error:", exc)
115
+ ```
116
+
117
+ Model parsing errors will raise normal Python exceptions — wrap calls where you need robust failure handling.
118
+
119
+ ## Common methods (short reference)
120
+
121
+ - HiveClient(username, password, hive_url, **kwargs) — construct and authenticate client
122
+ - get_programs(...)
123
+ - get_program(program_id)
124
+ - get_subjects(...)
125
+ - get_modules(...)
126
+ - get_exercises(...)
127
+ - get_exercise(exercise_id)
128
+ - get_exercise_fields(exercise)
129
+ - get_assignments(...)
130
+ - get_assignment(assignment_id)
131
+ - get_assignment_responses(assignment)
132
+ - get_users(...)
133
+ - get_classes(...)
134
+
135
+ Return values are typed model objects from `src/types` or generators of those objects.
136
+
137
+ ## Try it locally / Run tests
138
+
139
+ Install dev dependencies and run tests with pytest:
140
+
141
+ ```pwsh
142
+ pip install -e .
143
+ pytest -q
144
+ ```
145
+
146
+ ## Troubleshooting
147
+
148
+ - Authentication errors: verify username/password and the `hive_url` base. The client authenticates at construction.
149
+ - SSL verification: pass `verify=False` to the client constructor for self-signed servers (not recommended for production).
150
+
151
+ ## Contributing & development
152
+
153
+ - Tests live under `tests/` and show common usage patterns. Use them as examples.
154
+ - The typed models are in `src/types` and the `HiveClient` convenience layer is in `pyhive/client.py`.
155
+
156
+ If you plan to make changes, please add tests for new behavior and keep changes small and focused.