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,250 @@
1
+ """Authentication helpers and a small authenticated HTTP client for Hive.
2
+
3
+ This module provides decorators that add retry and token-refresh behavior to
4
+ HTTP calls and an internal ``_AuthenticatedHiveClient`` which wraps an
5
+ :class:`httpx.Client` and handles login/refresh for the Hive API.
6
+ """
7
+
8
+ import functools
9
+ import time
10
+ from collections.abc import Callable
11
+ from types import TracebackType
12
+ from typing import Any, TypeVar, cast
13
+
14
+ import httpx
15
+
16
+ F = TypeVar("F", bound=Callable[..., httpx.Response])
17
+
18
+ MAX_RETRIES_ON_SERVER_ERRORS = 5
19
+ INITIAL_BACKOFF_SECONDS = 0.5
20
+
21
+
22
+ def _retry_on_bad_gateway(func: F) -> F:
23
+ """Decorator: retry a request when the server returns HTTP 502.
24
+
25
+ The wrapped function is expected to return an :class:`httpx.Response`.
26
+ Retries use exponential backoff and will re-raise the final response's
27
+ HTTP error if all retries fail.
28
+ """
29
+
30
+ @functools.wraps(func)
31
+ def wrapper(self: "_AuthenticatedHiveClient", *args: Any, **kwargs: Any):
32
+ delay = INITIAL_BACKOFF_SECONDS
33
+ if MAX_RETRIES_ON_SERVER_ERRORS <= 0:
34
+ raise ValueError("MAX_RETRIES_ON_SERVER_ERRORS must be greater than 0")
35
+ response = None
36
+ for attempt in range(MAX_RETRIES_ON_SERVER_ERRORS):
37
+ response = func(self, *args, **kwargs)
38
+ if response.status_code != httpx.codes.BAD_GATEWAY.value:
39
+ return response
40
+ if attempt < MAX_RETRIES_ON_SERVER_ERRORS - 1:
41
+ time.sleep(delay)
42
+ delay *= 2
43
+ assert response is not None
44
+ response.raise_for_status()
45
+ return response
46
+
47
+ return cast("F", wrapper)
48
+
49
+
50
+ def _refresh_token_on_unauthorized(func: F) -> F:
51
+ """Decorator: refresh access token and retry on HTTP 401 Unauthorized.
52
+
53
+ If the wrapped function returns a 401 status, the client's
54
+ ``_refresh_access_token`` is called and the request is retried once.
55
+ """
56
+
57
+ @functools.wraps(func)
58
+ def wrapper(self: "_AuthenticatedHiveClient", *args: Any, **kwargs: Any):
59
+ response = func(self, *args, **kwargs)
60
+ if response.status_code == httpx.codes.UNAUTHORIZED.value:
61
+ self._refresh_access_token() # pylint: disable=protected-access
62
+ response = func(self, *args, **kwargs)
63
+ response.raise_for_status()
64
+ return response
65
+
66
+ return cast("F", wrapper)
67
+
68
+
69
+ def _with_retries_and_token_refresh(func: F) -> F:
70
+ """Compose the retry and token-refresh decorators.
71
+
72
+ Use this to wrap HTTP methods so they automatically handle transient
73
+ 502 errors and expired access tokens.
74
+ """
75
+
76
+ return _refresh_token_on_unauthorized(_retry_on_bad_gateway(func))
77
+
78
+
79
+ class _AuthenticatedHiveClient:
80
+ """Internal class used to handle authentication and re-authentication with Hive web endpoint."""
81
+
82
+ _refresh_token: str
83
+ _access_token: str
84
+ _session: httpx.Client
85
+ username: str
86
+
87
+ def __init__( # pylint: disable=too-many-arguments
88
+ self,
89
+ username: str,
90
+ password: str,
91
+ hive_url: str,
92
+ *,
93
+ timeout: httpx.Timeout | float | None = None,
94
+ headers: dict[str, str] | None = None,
95
+ verify: bool | str | None = None,
96
+ **kwargs: Any,
97
+ ) -> None:
98
+ """Create an authenticated client.
99
+
100
+ Common HTTP client options may be provided explicitly (typed) or via
101
+ ``**kwargs`` and will be forwarded to :class:`httpx.Client`.
102
+
103
+ Typed kwargs provided (timeout, headers, verify) take precedence; the
104
+ rest are forwarded from ``kwargs``.
105
+ """
106
+ self.username = username
107
+ self.hive_url = hive_url
108
+
109
+ client_kwargs: dict[str, Any] = {}
110
+ if timeout is not None:
111
+ client_kwargs["timeout"] = timeout
112
+ if headers is not None:
113
+ client_kwargs["headers"] = headers
114
+ if verify is not None:
115
+ client_kwargs["verify"] = verify
116
+
117
+ # Include any other httpx.Client kwargs passed in **kwargs
118
+ client_kwargs.update(kwargs)
119
+
120
+ self._session = httpx.Client(
121
+ base_url=hive_url,
122
+ **client_kwargs,
123
+ ).__enter__()
124
+ self._login(username, password)
125
+
126
+ def __enter__(self) -> "_AuthenticatedHiveClient":
127
+ """Enter context manager and return this client instance.
128
+
129
+ The underlying :class:`httpx.Client` is managed by this object's
130
+ lifecycle; entering the context returns the authenticated client so
131
+ callers can perform API calls.
132
+ """
133
+
134
+ return self
135
+
136
+ def __exit__(
137
+ self,
138
+ type_: type[BaseException] | None,
139
+ value: BaseException | None,
140
+ traceback: TracebackType | None,
141
+ ) -> bool | None:
142
+ """Exit the context and close the underlying httpx session.
143
+
144
+ This delegates to the managed :class:`httpx.Client`'s ``__exit__``
145
+ method to ensure resources are released.
146
+ """
147
+
148
+ self._session.__exit__(type_, value, traceback)
149
+
150
+ def _login(self, username: str, password: str) -> None:
151
+ """Perform an authentication request and store access/refresh tokens.
152
+
153
+ This sets the ``Authorization`` header on the underlying session.
154
+ """
155
+
156
+ response = self._session.post(
157
+ "/api/core/token/",
158
+ json={"username": username, "password": password},
159
+ )
160
+ response.raise_for_status()
161
+ data = response.json()
162
+ self._access_token = data["access"]
163
+ self._refresh_token = data["refresh"]
164
+
165
+ self._session.headers.update({"Authorization": f"Bearer {self._access_token}"})
166
+
167
+ def _refresh_access_token(self) -> None:
168
+ """Refresh the access token using the stored refresh token.
169
+
170
+ Updates the stored access and refresh tokens and the session header.
171
+ """
172
+
173
+ response = self._session.post(
174
+ "/api/core/token/refresh/",
175
+ json={"refresh": self._refresh_token},
176
+ )
177
+ response.raise_for_status()
178
+ data = response.json()
179
+ self._access_token = data["access"]
180
+ self._refresh_token = data["refresh"]
181
+
182
+ self._session.headers.update({"Authorization": f"Bearer {self._access_token}"})
183
+
184
+ @_with_retries_and_token_refresh
185
+ def _get(
186
+ self, endpoint: str, params: httpx.QueryParams | None = None
187
+ ) -> httpx.Response:
188
+ """Low-level GET that returns an :class:`httpx.Response`.
189
+
190
+ This is decorated to handle retries and token refresh automatically.
191
+ """
192
+
193
+ return self._session.get(endpoint, params=params, headers={"Accept": "application/json"})
194
+
195
+ @_with_retries_and_token_refresh
196
+ def _post(self, endpoint: str, data: dict[Any, Any]) -> httpx.Response:
197
+ """Low-level POST that returns an :class:`httpx.Response` with JSON body.
198
+
199
+ The ``data`` is JSON-encoded into the request body.
200
+ """
201
+
202
+ return self._session.post(endpoint, json=data)
203
+
204
+ @_with_retries_and_token_refresh
205
+ def _patch(self, endpoint: str, data: dict[Any, Any]) -> httpx.Response:
206
+ """Low-level PATCH request; returns :class:`httpx.Response`.
207
+
208
+ The ``data`` is JSON-encoded into the request body.
209
+ """
210
+
211
+ return self._session.patch(endpoint, json=data)
212
+
213
+ @_with_retries_and_token_refresh
214
+ def _delete(self, endpoint: str) -> httpx.Response:
215
+ """Low-level DELETE request; returns :class:`httpx.Response`."""
216
+
217
+ return self._session.delete(endpoint)
218
+
219
+ @_with_retries_and_token_refresh
220
+ def _put(self, endpoint: str, data: dict[Any, Any]) -> httpx.Response:
221
+ """Low-level PUT request; returns :class:`httpx.Response`.
222
+
223
+ The ``data`` is JSON-encoded into the request body.
224
+ """
225
+
226
+ return self._session.put(endpoint, json=data)
227
+
228
+ def get(self, endpoint: str, params: httpx.QueryParams | None = None) -> Any:
229
+ """High-level GET that returns parsed JSON from the response.
230
+
231
+ This calls the decorated ``_get`` helper and returns its JSON body.
232
+ """
233
+
234
+ return self._get(endpoint, params).json()
235
+
236
+ def post(self, endpoint: str, data: dict[Any, Any]) -> Any:
237
+ """High-level POST that returns parsed JSON from the response.
238
+
239
+ The ``data`` dict is JSON-encoded for the request body.
240
+ """
241
+
242
+ return self._post(endpoint, data).json()
243
+
244
+ def __repr__(self) -> str:
245
+ """Return a short representation including username and hive_url.
246
+
247
+ The representation intentionally omits secrets.
248
+ """
249
+
250
+ return f"HiveClient({self.username!r}, input(), {self.hive_url!r})"
File without changes
@@ -0,0 +1,210 @@
1
+ """Defines the Assignment type and related logic for representing student assignments in the Hive API."""
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 define, field
8
+ from dateutil.parser import isoparse
9
+ from .common import UNSET, Unset
10
+ from .core_item import HiveCoreItem
11
+ from .enums.assignment_status_enum import AssignmentStatusEnum
12
+ from .notification_nested import NotificationNested
13
+
14
+ if TYPE_CHECKING:
15
+ from ...client import HiveClient
16
+ from .exercise import Exercise
17
+ from .user import User
18
+ from .assignment_response import AssignmentResponse
19
+
20
+ T = TypeVar("T", bound="Assignment")
21
+
22
+
23
+ @define
24
+ class Assignment(HiveCoreItem):
25
+ """Represents a student's assignment for an exercise.
26
+
27
+ Attributes:
28
+ hive_client: Reference to the Hive API client.
29
+ id: Unique assignment ID.
30
+ user_id: ID of the assigned student.
31
+ checker_id: ID of the assigned checker, or None.
32
+ checker_first_name: First name of the checker.
33
+ checker_last_name: Last name of the checker.
34
+ is_subscribed: Whether the student is subscribed to updates.
35
+ exercise_id: ID of the exercise.
36
+ assignment_status: Current state of the assignment.
37
+ patbas: Whether it's a PATBAS assignment.
38
+ notifications: List of related notifications.
39
+ last_staff_updated: Timestamp of the last staff update.
40
+ work_time: Total work time in minutes.
41
+ student_assignment_status: The student's view of the assignment status.
42
+ description: Optional text description.
43
+ submission_count: Total number of submissions.
44
+ total_check_count: Number of total checks.
45
+ manual_check_count: Number of manual checks.
46
+ flagged: Whether the assignment is flagged for review.
47
+ timer: Optional timer state string.
48
+
49
+ """
50
+
51
+ hive_client: "HiveClient"
52
+ id: int
53
+ user_id: int
54
+ checker_id: None | int
55
+ checker_first_name: str
56
+ checker_last_name: str
57
+ is_subscribed: bool
58
+ exercise_id: int
59
+ assignment_status: AssignmentStatusEnum
60
+ patbas: bool
61
+ notifications: list["NotificationNested"]
62
+ last_staff_updated: datetime.datetime
63
+ work_time: int
64
+ student_assignment_status: Unset | AssignmentStatusEnum = UNSET
65
+ description: None | Unset | str = UNSET
66
+ submission_count: Unset | int = UNSET
67
+ total_check_count: Unset | int = UNSET
68
+ manual_check_count: Unset | int = UNSET
69
+ flagged: Unset | bool = UNSET
70
+ timer: None | Unset | str = UNSET
71
+
72
+ # Lazy-loaded objects
73
+ _user: "User | None" = field(init=False, default=None)
74
+ _checker: "User | None" = field(init=False, default=None)
75
+ _exercise: "Exercise | None" = field(init=False, default=None)
76
+
77
+ @property
78
+ def user(self) -> "User":
79
+ """Lazily load and return the user this assignment belongs to."""
80
+ if self._user is None:
81
+ self._user = self.hive_client.get_user(self.user_id)
82
+ return self._user
83
+
84
+ @property
85
+ def checker(self) -> "User | None":
86
+ """Lazily load and return the checker (if any) assigned to this assignment."""
87
+ if self.checker_id is None:
88
+ return None
89
+ if self._checker is None:
90
+ self._checker = self.hive_client.get_user(self.checker_id)
91
+ return self._checker
92
+
93
+ @property
94
+ def exercise(self) -> "Exercise":
95
+ """Lazily load and return the exercise associated with this assignment."""
96
+ if self._exercise is None:
97
+ self._exercise = self.hive_client.get_exercise(self.exercise_id)
98
+ return self._exercise
99
+
100
+ def to_dict(self) -> dict[str, Any]:
101
+ """Serialize Assignment to a dictionary."""
102
+ result: dict[str, None | str | int | list[dict[str, Any]]] = {
103
+ "id": self.id,
104
+ "user": self.user_id,
105
+ "checker": self.checker_id,
106
+ "checker_first_name": self.checker_first_name,
107
+ "checker_last_name": self.checker_last_name,
108
+ "is_subscribed": self.is_subscribed,
109
+ "exercise": self.exercise_id,
110
+ "assignment_status": self.assignment_status.value,
111
+ "patbas": self.patbas,
112
+ "notifications": [n.to_dict() for n in self.notifications],
113
+ "last_staff_updated": self.last_staff_updated.isoformat(),
114
+ "work_time": self.work_time,
115
+ }
116
+
117
+ # Conditionally include optional/unset fields
118
+ if not isinstance(self.student_assignment_status, Unset):
119
+ result["student_assignment_status"] = self.student_assignment_status.value
120
+ if not isinstance(self.description, Unset):
121
+ result["description"] = self.description
122
+ if not isinstance(self.submission_count, Unset):
123
+ result["submission_count"] = self.submission_count
124
+ if not isinstance(self.total_check_count, Unset):
125
+ result["total_check_count"] = self.total_check_count
126
+ if not isinstance(self.manual_check_count, Unset):
127
+ result["manual_check_count"] = self.manual_check_count
128
+ if not isinstance(self.flagged, Unset):
129
+ result["flagged"] = self.flagged
130
+ if not isinstance(self.timer, Unset):
131
+ result["timer"] = self.timer
132
+
133
+ return result
134
+
135
+ @classmethod
136
+ def from_dict(cls, src_dict: Mapping[str, Any], hive_client: "HiveClient") -> Self:
137
+ """Deserialize Assignment from a dictionary."""
138
+
139
+ d = dict(src_dict)
140
+
141
+ notifications = [
142
+ NotificationNested.from_dict(n, hive_client=hive_client)
143
+ for n in d.pop("notifications", [])
144
+ ]
145
+
146
+ student_assignment_status = (
147
+ AssignmentStatusEnum(d["student_assignment_status"])
148
+ if "student_assignment_status" in d
149
+ and not isinstance(d["student_assignment_status"], Unset)
150
+ else UNSET
151
+ )
152
+
153
+ description = d.pop("description", UNSET)
154
+ submission_count = d.pop("submission_count", UNSET)
155
+ total_check_count = d.pop("total_check_count", UNSET)
156
+ manual_check_count = d.pop("manual_check_count", UNSET)
157
+ flagged = d.pop("flagged", UNSET)
158
+ timer = d.pop("timer", UNSET)
159
+
160
+ return cls(
161
+ hive_client=hive_client,
162
+ id=d["id"],
163
+ user_id=d["user"],
164
+ checker_id=cast("int | None", d["checker"]),
165
+ checker_first_name=d["checker_first_name"],
166
+ checker_last_name=d["checker_last_name"],
167
+ is_subscribed=d["is_subscribed"],
168
+ exercise_id=d["exercise"],
169
+ assignment_status=AssignmentStatusEnum(d["assignment_status"]),
170
+ patbas=d["patbas"],
171
+ notifications=notifications,
172
+ last_staff_updated=isoparse(d["last_staff_updated"]),
173
+ work_time=d["work_time"],
174
+ student_assignment_status=student_assignment_status,
175
+ description=cast("str | None | Unset", description),
176
+ submission_count=cast("int | Unset", submission_count),
177
+ total_check_count=cast("int | Unset", total_check_count),
178
+ manual_check_count=cast("int | Unset", manual_check_count),
179
+ flagged=cast("bool | Unset", flagged),
180
+ timer=cast("str | None | Unset", timer),
181
+ )
182
+
183
+ def __eq__(self, value: object) -> bool:
184
+ if not isinstance(value, Assignment):
185
+ return False
186
+ return (
187
+ self.id == value.id
188
+ and self.exercise_id == value.exercise_id
189
+ and self.user_id == value.user_id
190
+ and self.checker_id == value.checker_id
191
+ and self.assignment_status == value.assignment_status
192
+ and self.exercise == value.exercise
193
+ )
194
+
195
+ def __lt__(self, value: object) -> bool:
196
+ if not isinstance(value, Assignment):
197
+ return NotImplemented
198
+ return self.user.number < value.user.number
199
+
200
+ def get_responses(self) -> Generator["AssignmentResponse", None, None]:
201
+ """Fetch all responses to this assignment.
202
+ Responses include both student and mentor submissions, comments, WIP, ..."""
203
+ return self.hive_client.get_assignment_responses(assignment=self.id)
204
+
205
+ def __iter__(self) -> Generator["Assignment", None, None]:
206
+ """Allow iteration over this Assignment to yield its responses."""
207
+ yield from self.get_responses()
208
+
209
+
210
+ AssignmentLike = TypeVar("AssignmentLike", Assignment, int)
@@ -0,0 +1,205 @@
1
+ """Responses to assignments given to students."""
2
+
3
+ import datetime
4
+ from typing import TYPE_CHECKING, Any, TypeVar, Union, Generator
5
+ from attrs import define, field
6
+ from dateutil.parser import isoparse
7
+
8
+ from .core_item import HiveCoreItem
9
+ from .autocheck_status import AutoCheckStatus
10
+ from .assignment import Assignment
11
+ from .enums.assignment_response_type_enum import AssignmentResponseTypeEnum
12
+ from .common import UNSET, Unset
13
+
14
+ if TYPE_CHECKING:
15
+ from .user import User
16
+ from .assignment_response_content import AssignmentResponseContent
17
+ from ...client import HiveClient
18
+
19
+
20
+ T = TypeVar("T", bound="AssignmentResponse")
21
+
22
+
23
+ @define
24
+ class AssignmentResponse(HiveCoreItem):
25
+ """
26
+ Attributes:
27
+ id (int):
28
+ user_id (int):
29
+ contents (list['AssignmentResponseContent']):
30
+ date (datetime.datetime):
31
+ response_type (AssignmentResponseTypeEnum):
32
+ * `Comment` - Comment
33
+ * `Work In Progress` - Workinprogress
34
+ * `Submission` - Submission
35
+ * `AutoCheck` - Autocheck
36
+ * `Redo` - Redo
37
+ * `Done` - Done
38
+ autocheck_statuses (Union[None, list['Status']]):
39
+ file_name (Union[Unset, str]):
40
+ dear_student (Union[Unset, bool]): Default: True.
41
+ hide_checker_name (Union[Unset, bool]):
42
+ segel_only (Union[Unset, bool]):
43
+ """
44
+
45
+ hive_client: "HiveClient"
46
+ assignment_id: int
47
+ id: int
48
+ user_id: int
49
+ contents: list["AssignmentResponseContent"]
50
+ date: datetime.datetime
51
+ response_type: AssignmentResponseTypeEnum
52
+ autocheck_statuses: Union[None, list["AutoCheckStatus"]]
53
+ file_name: Union[Unset, str] = UNSET
54
+ dear_student: Union[Unset, bool] = True
55
+ hide_checker_name: Union[Unset, bool] = UNSET
56
+ segel_only: Union[Unset, bool] = UNSET
57
+
58
+ # Lazy-loaded objects
59
+ _user: "User | None" = field(init=False, default=None)
60
+ _assignment: "Assignment | None" = field(init=False, default=None)
61
+
62
+ def to_dict(self) -> dict[str, Any]:
63
+ contents = []
64
+ for contents_item_data in self.contents:
65
+ contents_item = contents_item_data.to_dict()
66
+ contents.append(contents_item)
67
+
68
+ autocheck_statuses: Union[None, list[dict[str, Any]]]
69
+ if isinstance(self.autocheck_statuses, list):
70
+ autocheck_statuses = []
71
+ for autocheck_statuses_type_0_item_data in self.autocheck_statuses:
72
+ autocheck_statuses_type_0_item = (
73
+ autocheck_statuses_type_0_item_data.to_dict()
74
+ )
75
+ autocheck_statuses.append(autocheck_statuses_type_0_item)
76
+
77
+ else:
78
+ autocheck_statuses = self.autocheck_statuses
79
+
80
+ file_name = self.file_name
81
+
82
+ dear_student = self.dear_student
83
+
84
+ hide_checker_name = self.hide_checker_name
85
+
86
+ segel_only = self.segel_only
87
+
88
+ field_dict: dict[str, Any] = {}
89
+ field_dict.update(
90
+ {
91
+ "id": self.id,
92
+ "user": self.user,
93
+ "contents": contents,
94
+ "date": self.date.isoformat(),
95
+ "response_type": self.response_type.value,
96
+ "autocheck_statuses": autocheck_statuses,
97
+ }
98
+ )
99
+ if file_name is not UNSET:
100
+ field_dict["file_name"] = file_name
101
+ if dear_student is not UNSET:
102
+ field_dict["dear_student"] = dear_student
103
+ if hide_checker_name is not UNSET:
104
+ field_dict["hide_checker_name"] = hide_checker_name
105
+ if segel_only is not UNSET:
106
+ field_dict["segel_only"] = segel_only
107
+
108
+ return field_dict
109
+
110
+ @classmethod
111
+ def from_dict( # pylint: disable=too-many-locals, arguments-differ
112
+ cls: type[T],
113
+ src_dict: dict[str, Any],
114
+ assignment_id: int,
115
+ hive_client: "HiveClient",
116
+ ) -> T:
117
+ from .assignment_response_content import (
118
+ AssignmentResponseContent,
119
+ ) # pylint: disable=import-outside-toplevel
120
+
121
+ d = dict(src_dict)
122
+ id = d.pop("id")
123
+
124
+ user_id = d.pop("user")
125
+
126
+ contents = []
127
+ _contents = d.pop("contents")
128
+ if not isinstance(_contents, list):
129
+ raise TypeError(
130
+ f"Assignment response contents must be a list, not {type(_contents)}"
131
+ )
132
+ for contents_item_data in _contents:
133
+ contents_item = AssignmentResponseContent.from_dict(
134
+ contents_item_data,
135
+ assignment=assignment_id,
136
+ assignment_response_id=id,
137
+ hive_client=hive_client,
138
+ )
139
+ contents.append(contents_item)
140
+ date = isoparse(d.pop("date"))
141
+ response_type = AssignmentResponseTypeEnum(d.pop("response_type"))
142
+
143
+ def _parse_autocheck_statuses(
144
+ data: object,
145
+ ) -> Union[None, list["AutoCheckStatus"]]:
146
+ if data is None:
147
+ return data
148
+ if not isinstance(data, list):
149
+ raise TypeError(f"Autocheck statuses must be a list, not {type(data)}")
150
+ autocheck_statuses_type_0 = []
151
+ _autocheck_statuses_type_0 = data
152
+ for autocheck_statuses_type_0_item_data in _autocheck_statuses_type_0:
153
+ autocheck_statuses_type_0_item = AutoCheckStatus.from_dict(
154
+ autocheck_statuses_type_0_item_data,
155
+ hive_client=hive_client,
156
+ )
157
+
158
+ autocheck_statuses_type_0.append(autocheck_statuses_type_0_item)
159
+
160
+ return autocheck_statuses_type_0
161
+
162
+ autocheck_statuses = _parse_autocheck_statuses(d.pop("autocheck_statuses"))
163
+
164
+ file_name = d.pop("file_name", UNSET)
165
+
166
+ dear_student = d.pop("dear_student", UNSET)
167
+
168
+ hide_checker_name = d.pop("hide_checker_name", UNSET)
169
+
170
+ segel_only = d.pop("segel_only", UNSET)
171
+
172
+ return cls(
173
+ hive_client=hive_client,
174
+ assignment_id=assignment_id,
175
+ id=id,
176
+ user_id=user_id,
177
+ contents=contents,
178
+ date=date,
179
+ response_type=response_type,
180
+ autocheck_statuses=autocheck_statuses,
181
+ file_name=file_name,
182
+ dear_student=dear_student,
183
+ hide_checker_name=hide_checker_name,
184
+ segel_only=segel_only,
185
+ )
186
+
187
+ @property
188
+ def user(self) -> "User":
189
+ """Lazily load and return the user this assignment belongs to."""
190
+ if self._user is None:
191
+ self._user = self.hive_client.get_user(self.user_id)
192
+ return self._user
193
+
194
+ @property
195
+ def assignment(self) -> "Assignment":
196
+ """Lazily load and return the assignment this response belongs to."""
197
+ if self._assignment is None:
198
+ self._assignment = self.hive_client.get_assignment(
199
+ assignment_id=self.assignment_id
200
+ )
201
+ return self._assignment
202
+
203
+ def __iter__(self) -> Generator["AssignmentResponseContent", None, None]:
204
+ """Allow iteration over this AssignmentResponse to yield its contents."""
205
+ yield from self.contents