PyHiveLMS 1.0.2__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 (69) hide show
  1. pyhive/__init__.py +13 -0
  2. pyhive/cli/__init__.py +0 -0
  3. pyhive/cli/main.py +31 -0
  4. pyhive/client/__init__.py +112 -0
  5. pyhive/client/assignment_responses.py +45 -0
  6. pyhive/client/assignments.py +115 -0
  7. pyhive/client/classes.py +108 -0
  8. pyhive/client/client_shared.py +75 -0
  9. pyhive/client/exercises.py +82 -0
  10. pyhive/client/fields.py +41 -0
  11. pyhive/client/help.py +191 -0
  12. pyhive/client/modules.py +84 -0
  13. pyhive/client/programs.py +116 -0
  14. pyhive/client/queues.py +21 -0
  15. pyhive/client/subjects.py +101 -0
  16. pyhive/client/users.py +307 -0
  17. pyhive/client/utils.py +42 -0
  18. pyhive/client/version.py +20 -0
  19. pyhive/src/__init__.py +0 -0
  20. pyhive/src/_generated_versions.py +3 -0
  21. pyhive/src/api_versions.py +6 -0
  22. pyhive/src/authenticated_hive_client.py +252 -0
  23. pyhive/src/types/__init__.py +0 -0
  24. pyhive/src/types/assignment.py +214 -0
  25. pyhive/src/types/assignment_response.py +205 -0
  26. pyhive/src/types/assignment_response_content.py +131 -0
  27. pyhive/src/types/autocheck_status.py +95 -0
  28. pyhive/src/types/class_.py +116 -0
  29. pyhive/src/types/common.py +56 -0
  30. pyhive/src/types/core_item.py +22 -0
  31. pyhive/src/types/enums/__init__.py +5 -0
  32. pyhive/src/types/enums/action_enum.py +18 -0
  33. pyhive/src/types/enums/assignment_response_type_enum.py +17 -0
  34. pyhive/src/types/enums/assignment_status_enum.py +17 -0
  35. pyhive/src/types/enums/class_type_enum.py +13 -0
  36. pyhive/src/types/enums/clearance_enum.py +15 -0
  37. pyhive/src/types/enums/event_type_enum.py +14 -0
  38. pyhive/src/types/enums/exercise_patbas_enum.py +15 -0
  39. pyhive/src/types/enums/exercise_preview_types.py +15 -0
  40. pyhive/src/types/enums/form_field_type_enum.py +15 -0
  41. pyhive/src/types/enums/gender_enum.py +14 -0
  42. pyhive/src/types/enums/help_response_type_enum.py +14 -0
  43. pyhive/src/types/enums/help_status_enum.py +13 -0
  44. pyhive/src/types/enums/help_type_enum.py +18 -0
  45. pyhive/src/types/enums/queue_rule_enum.py +15 -0
  46. pyhive/src/types/enums/status_enum.py +21 -0
  47. pyhive/src/types/enums/sync_status_enum.py +15 -0
  48. pyhive/src/types/enums/visibility_enum.py +14 -0
  49. pyhive/src/types/event.py +142 -0
  50. pyhive/src/types/event_attendees_type_0_item.py +69 -0
  51. pyhive/src/types/event_color.py +64 -0
  52. pyhive/src/types/exercise.py +217 -0
  53. pyhive/src/types/form_field.py +152 -0
  54. pyhive/src/types/help_.py +279 -0
  55. pyhive/src/types/help_response.py +113 -0
  56. pyhive/src/types/help_response_segel_nested.py +130 -0
  57. pyhive/src/types/module.py +141 -0
  58. pyhive/src/types/notification_nested.py +80 -0
  59. pyhive/src/types/program.py +186 -0
  60. pyhive/src/types/queue.py +152 -0
  61. pyhive/src/types/queue_item.py +87 -0
  62. pyhive/src/types/subject.py +163 -0
  63. pyhive/src/types/tag.py +63 -0
  64. pyhive/src/types/user.py +455 -0
  65. pyhive/types.py +31 -0
  66. pyhivelms-1.0.2.dist-info/METADATA +156 -0
  67. pyhivelms-1.0.2.dist-info/RECORD +69 -0
  68. pyhivelms-1.0.2.dist-info/WHEEL +4 -0
  69. pyhivelms-1.0.2.dist-info/entry_points.txt +2 -0
pyhive/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ """Public package for the pyhive distribution.
2
+
3
+ Expose the public convenience symbol `HiveClient` at package level so users
4
+ can do `from pyhive import HiveClient`.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ # Import the implementation from the `pyhive` package (implementation
10
+ # lives there) and expose the client at package level.
11
+ from pyhive.client import HiveClient # re-export
12
+
13
+ __all__ = ["HiveClient"]
pyhive/cli/__init__.py ADDED
File without changes
pyhive/cli/main.py ADDED
@@ -0,0 +1,31 @@
1
+ """PyHive CLI entry point"""
2
+
3
+ import typer
4
+
5
+ from pyhive.src._generated_versions import SUPPORTED_API_VERSIONS
6
+
7
+ app = typer.Typer(help="PyHive CLI")
8
+
9
+
10
+ @app.command()
11
+ def versions():
12
+ """Show supported Hive versions"""
13
+ print("Supported Hive Versions:")
14
+ for v in SUPPORTED_API_VERSIONS:
15
+ print(v)
16
+
17
+
18
+ @app.command()
19
+ def versions2():
20
+ """Show supported API versions"""
21
+ for v in SUPPORTED_API_VERSIONS:
22
+ print(v)
23
+
24
+
25
+ def main():
26
+ """PyHive CLI main entry point"""
27
+ app()
28
+
29
+
30
+ if __name__ == "__main__":
31
+ main()
@@ -0,0 +1,112 @@
1
+ """High-level Hive API client aggregator."""
2
+
3
+ from types import TracebackType
4
+ from typing import TYPE_CHECKING, Optional, Union
5
+
6
+ from ..src.api_versions import (LATEST_API_VERSION, MIN_API_VERSION,
7
+ SUPPORTED_API_VERSIONS)
8
+ from .assignment_responses import AssignmentResponsesClientMixin
9
+ from .assignments import AssignmentClientMixin
10
+ from .classes import ClassesClientMixin
11
+ from .exercises import ExerciseClientMixin
12
+ from .fields import FieldsClientMixin
13
+ from .help import HelpClientMixin
14
+ from .modules import ModuleClientMixin
15
+ from .programs import ProgramClientMixin
16
+ from .queues import QueuesClientMixin
17
+ from .subjects import SubjectClientMixin
18
+ from .users import UserClientMixin
19
+ from .version import VersionClientMixin
20
+
21
+ if TYPE_CHECKING:
22
+ from httpx import Timeout
23
+ from httpx._types import ProxyTypes
24
+
25
+
26
+ class HiveClient( # pylint: disable=too-many-ancestors,abstract-method
27
+ ProgramClientMixin,
28
+ SubjectClientMixin,
29
+ ModuleClientMixin,
30
+ ExerciseClientMixin,
31
+ AssignmentClientMixin,
32
+ UserClientMixin,
33
+ ClassesClientMixin,
34
+ FieldsClientMixin,
35
+ AssignmentResponsesClientMixin,
36
+ QueuesClientMixin,
37
+ HelpClientMixin,
38
+ VersionClientMixin,
39
+ ):
40
+ """Aggregated HTTP client for accessing Hive API resources."""
41
+
42
+ def __init__(
43
+ self,
44
+ *args,
45
+ skip_version_check: bool = False,
46
+ timeout: Optional[Union["Timeout", float]] = None,
47
+ headers: Optional[dict[str, str]] = None,
48
+ verify: Optional[Union[bool, str]] = None,
49
+ proxy: Optional["ProxyTypes"] = None,
50
+ **kwargs,
51
+ ):
52
+ super().__init__(
53
+ *args,
54
+ timeout=timeout,
55
+ headers=headers,
56
+ verify=verify,
57
+ proxy=proxy,
58
+ **kwargs,
59
+ )
60
+ if not skip_version_check:
61
+ self._api_version_check()
62
+
63
+ def __repr__(self) -> str:
64
+ """Return a short representation including username and hive_url.
65
+
66
+ The representation intentionally omits secrets.
67
+ """
68
+
69
+ return f"HiveClient({self.username!r}, input(), {self.hive_url!r})"
70
+
71
+ def __enter__(self) -> "HiveClient":
72
+ """Enter context manager and return this client instance.
73
+
74
+ The underlying :class:`httpx.Client` is managed by this object's
75
+ lifecycle; entering the context returns the authenticated client so
76
+ callers can perform API calls.
77
+ """
78
+ return self
79
+
80
+ def __exit__(
81
+ self,
82
+ type_: type[BaseException] | None,
83
+ value: BaseException | None,
84
+ traceback: TracebackType | None,
85
+ ) -> None:
86
+ """Exit the context and close the underlying httpx session.
87
+
88
+ This delegates to the managed :class:`httpx.Client`'s ``__exit__``
89
+ method to ensure resources are released.
90
+ """
91
+
92
+ self._session.__exit__(type_, value, traceback)
93
+
94
+ def _api_version_check(self) -> None:
95
+ """Validate that the Hive server API version is supported.
96
+
97
+ Fetches the server version via ``get_hive_version`` and verifies it is present
98
+ in ``SUPPORTED_API_VERSIONS``. If unsupported, raises a RuntimeError with
99
+ guidance to align the client and server versions.
100
+
101
+ Raises:
102
+ RuntimeError: If the server API version is not supported by this client.
103
+ """
104
+ version_str = self.get_hive_version()
105
+ if version_str not in SUPPORTED_API_VERSIONS:
106
+ supported_range = f"{MIN_API_VERSION} .. {LATEST_API_VERSION}"
107
+ raise RuntimeError(
108
+ (
109
+ f"Unsupported Hive API version '{version_str}'. Supported versions: {supported_range}. "
110
+ f"Please upgrade/downgrade the server or use a compatible client."
111
+ )
112
+ )
@@ -0,0 +1,45 @@
1
+ """
2
+ Assignment Response resource mixin for HiveClient.
3
+
4
+ Provides methods for listing and retrieving AssignmentResponse records for a given assignment
5
+ through the Hive API. Intended only for use as a mixin on HiveClient.
6
+ """
7
+
8
+ from ..src.types.assignment_response import AssignmentResponse
9
+ from .client_shared import ClientCoreMixin
10
+ from .utils import resolve_item_or_id
11
+
12
+
13
+ class AssignmentResponsesClientMixin(ClientCoreMixin):
14
+ """
15
+ Mixin class providing assignment response API methods to HiveClient.
16
+
17
+ Methods
18
+ -------
19
+ get_assignment_responses(assignment)
20
+ List all assignment responses for a single assignment.
21
+ get_assignment_response(assignment, response_id)
22
+ Retrieve one assignment response by id for a given assignment.
23
+ """
24
+
25
+ def get_assignment_responses(self, assignment):
26
+ """Yield assignment responses for the provided ``assignment`` (id or instance)."""
27
+ assignment_id = resolve_item_or_id(assignment)
28
+ return self._get_core_items(
29
+ f"/api/core/assignments/{assignment_id}/responses/",
30
+ AssignmentResponse,
31
+ assignment_id=assignment_id,
32
+ )
33
+
34
+ def get_assignment_response(self, assignment, response_id: int):
35
+ """Return a single response by ``response_id`` for the given ``assignment``."""
36
+ from ..client import HiveClient
37
+
38
+ assert isinstance(self, HiveClient), "self must be an instance of HiveClient"
39
+
40
+ assignment_id = resolve_item_or_id(assignment)
41
+ return AssignmentResponse.from_dict(
42
+ self.get(f"/api/core/assignments/{assignment_id}/responses/{response_id}/"),
43
+ assignment_id=assignment_id,
44
+ hive_client=self,
45
+ )
@@ -0,0 +1,115 @@
1
+ """
2
+ Assignment resource mixin for HiveClient.
3
+
4
+ Adds methods for listing and retrieving Assignment records via the Hive API. Meant only
5
+ for use as a mixin on HiveClient.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, Iterable, Optional, Sequence
9
+
10
+ from ..src.types.assignment import Assignment
11
+ from .client_shared import ClientCoreMixin
12
+ from .utils import assert_mutually_exclusive_filters, resolve_item_or_id
13
+
14
+ if TYPE_CHECKING:
15
+ from ..src.types.module import ModuleLike
16
+ from ..src.types.subject import SubjectLike
17
+ from ..src.types.user import UserLike
18
+
19
+
20
+ class AssignmentClientMixin(ClientCoreMixin):
21
+ """
22
+ Mixin class providing assignment-related API methods to HiveClient.
23
+
24
+ Methods
25
+ -------
26
+ get_assignments(...various filters...)
27
+ List all or filtered assignments, supporting complex relational filters.
28
+ get_assignment(assignment_id)
29
+ Retrieve a single assignment by its id.
30
+ """
31
+
32
+ def get_assignments( # pylint: disable=too-many-arguments,too-many-locals
33
+ self,
34
+ *,
35
+ exercise__id: Optional[int] = None,
36
+ exercise__parent_module__id: Optional[int] = None,
37
+ exercise__parent_module__parent_subject__id: Optional[int] = None,
38
+ exercise__tags__id__in: Optional[Sequence[int]] = None,
39
+ queue__id: Optional[int] = None,
40
+ user__classes__id: Optional[int] = None,
41
+ user__classes__id__in: Optional[Sequence[int]] = None,
42
+ user__id__in: Optional[Sequence[int]] = None,
43
+ user__mentor__id: Optional[int] = None,
44
+ user__mentor__id__in: Optional[Sequence[int]] = None,
45
+ user__program__id__in: Optional[Sequence[int]] = None,
46
+ # Non built-in filters
47
+ parent_module: Optional["ModuleLike"] = None,
48
+ parent_subject: Optional["SubjectLike"] = None,
49
+ for_user: Optional["UserLike"] = None,
50
+ for_mentees_of: Optional["UserLike"] = None,
51
+ ) -> Iterable[Assignment]:
52
+ """Yield ``Assignment`` objects filtered by the provided criteria."""
53
+ from ..client import HiveClient
54
+
55
+ assert isinstance(self, HiveClient), "self must be an instance of HiveClient"
56
+
57
+ if parent_module is not None and exercise__parent_module__id is not None:
58
+ assert exercise__parent_module__id == resolve_item_or_id(parent_module)
59
+ exercise__parent_module__id = (
60
+ exercise__parent_module__id
61
+ if exercise__parent_module__id is not None
62
+ else resolve_item_or_id(parent_module)
63
+ )
64
+ if (
65
+ parent_subject is not None
66
+ and exercise__parent_module__parent_subject__id is not None
67
+ ):
68
+ assert exercise__parent_module__parent_subject__id == resolve_item_or_id(
69
+ parent_subject
70
+ )
71
+ exercise__parent_module__parent_subject__id = (
72
+ exercise__parent_module__parent_subject__id
73
+ if exercise__parent_module__parent_subject__id is not None
74
+ else resolve_item_or_id(parent_subject)
75
+ )
76
+
77
+ assert_mutually_exclusive_filters(user__classes__id, user__classes__id__in)
78
+
79
+ assert (not (user__id__in is not None and for_user is not None)) or (
80
+ len(user__id__in) == 1 and user__id__in[0] == resolve_item_or_id(for_user)
81
+ ), "Filters user__id__in and for_user conflict!"
82
+ if for_user is not None:
83
+ user__id__in = [resolve_item_or_id(for_user)]
84
+
85
+ assert_mutually_exclusive_filters(
86
+ user__mentor__id, user__mentor__id__in, for_mentees_of
87
+ )
88
+ if for_mentees_of is not None:
89
+ user__mentor__id = resolve_item_or_id(for_mentees_of)
90
+
91
+ return self._get_core_items(
92
+ "/api/core/assignments/",
93
+ Assignment,
94
+ exercise__id=exercise__id,
95
+ exercise__parent_module__id=exercise__parent_module__id,
96
+ exercise__parent_module__parent_subject__id=exercise__parent_module__parent_subject__id,
97
+ exercise__tags__id__in=exercise__tags__id__in,
98
+ queue__id=queue__id,
99
+ user__classes__id=user__classes__id,
100
+ user__classes__id__in=user__classes__id__in,
101
+ user__id__in=user__id__in,
102
+ user__mentor__id=user__mentor__id,
103
+ user__mentor__id__in=user__mentor__id__in,
104
+ user__program__id__in=user__program__id__in,
105
+ )
106
+
107
+ def get_assignment(self, assignment_id: int) -> Assignment:
108
+ """Return a single ``Assignment`` by its id."""
109
+ from ..client import HiveClient
110
+
111
+ assert isinstance(self, HiveClient), "self must be an instance of HiveClient"
112
+ return Assignment.from_dict(
113
+ self.get(f"/api/core/assignments/{assignment_id}/"),
114
+ hive_client=self,
115
+ )
@@ -0,0 +1,108 @@
1
+ """
2
+ Class resource mixin for HiveClient.
3
+
4
+ Provides listing and retrieval of Class records from the Hive API. Use only as a mixin for the main HiveClient.
5
+ """
6
+
7
+ from typing import TYPE_CHECKING, Iterable, Optional
8
+
9
+ from ..src.types.class_ import Class, ClassLike
10
+ from ..src.types.enums.class_type_enum import ClassTypeEnum
11
+ from .client_shared import ClientCoreMixin
12
+ from .utils import resolve_item_or_id
13
+
14
+ if TYPE_CHECKING:
15
+ from ..src.types.program import ProgramLike
16
+ from ..src.types.user import UserLike
17
+
18
+
19
+ class ClassesClientMixin(ClientCoreMixin):
20
+ """
21
+ Mixin class providing class-related API methods for HiveClient.
22
+
23
+ Methods
24
+ -------
25
+ get_classes(id__in=None, name=None, program__id__in=None, type_=None, ...)
26
+ List all or filtered classes via the Hive API. Supports multiple relationship filters.
27
+ get_class(class_id)
28
+ Retrieve a single class by id.
29
+ """
30
+
31
+ def get_classes(
32
+ self,
33
+ *,
34
+ id__in: Optional[list[int]] = None,
35
+ name: Optional[str] = None,
36
+ program__id__in: Optional[list[int]] = None,
37
+ type_: Optional[ClassTypeEnum] = None,
38
+ ) -> Iterable[Class]:
39
+ """Yield ``Class`` objects filtered by the provided criteria."""
40
+ from ..client import HiveClient
41
+
42
+ assert isinstance(self, HiveClient), "self must be an instance of HiveClient"
43
+ return self._get_core_items(
44
+ "/api/core/management/classes/",
45
+ Class,
46
+ id__in=id__in,
47
+ name=name,
48
+ program__id__in=program__id__in,
49
+ type_=type_,
50
+ )
51
+
52
+ def get_class(
53
+ self,
54
+ class_id: int,
55
+ ) -> Class:
56
+ """Return a single ``Class`` by its id."""
57
+ from ..client import HiveClient
58
+
59
+ assert isinstance(self, HiveClient), "self must be an instance of HiveClient"
60
+ return Class.from_dict(
61
+ self.get(f"/api/core/management/classes/{class_id}/"),
62
+ hive_client=self,
63
+ )
64
+
65
+ def create_class(
66
+ self,
67
+ name: str,
68
+ *,
69
+ program: "ProgramLike",
70
+ users: Optional[list["UserLike"]] = None,
71
+ email: Optional[str] = None,
72
+ type_: Optional[ClassTypeEnum] = None,
73
+ classes: Optional[list["ClassLike"]] = None,
74
+ description: Optional[str] = None,
75
+ ) -> Class:
76
+ """
77
+ Create a Class via the Hive API.
78
+ """
79
+
80
+ from ..client import HiveClient
81
+
82
+ assert isinstance(self, HiveClient), "self must be an instance of HiveClient"
83
+
84
+ payload: dict[str, object] = {
85
+ "name": name,
86
+ "program": resolve_item_or_id(program),
87
+ }
88
+
89
+ # Users list - include always, default to empty list like create_user does for mentees
90
+ if users is None:
91
+ users = []
92
+ payload["users"] = [resolve_item_or_id(u) for u in users]
93
+
94
+ # Optional fields
95
+ if email is not None:
96
+ payload["email"] = email
97
+ if type_ is not None:
98
+ payload["type"] = type_.value
99
+ if classes is not None:
100
+ payload["classes"] = [resolve_item_or_id(c) for c in classes]
101
+ if description is not None:
102
+ payload["description"] = description
103
+
104
+ response = self.post("/api/core/management/classes/", payload)
105
+ return Class.from_dict(response, hive_client=self)
106
+
107
+ def delete_class(self, class_: "ClassLike") -> None:
108
+ self.delete(f"/api/core/management/classes/{resolve_item_or_id(class_)}/")
@@ -0,0 +1,75 @@
1
+ """Shared client utilities and common mixin base for Hive API access.
2
+
3
+ - ``ClientCoreMixin``: base class that provides ``_get_core_items`` used by resource mixins.
4
+ """
5
+
6
+ from typing import Any, Generator, Iterable, Optional
7
+
8
+ import httpx
9
+
10
+ from ..src.authenticated_hive_client import AuthenticatedHiveClient
11
+ from .utils import CoreItemTypeT
12
+
13
+
14
+ class ClientCoreMixin(AuthenticatedHiveClient):
15
+ """Common mixin base that exposes ``_get_core_items`` for list endpoints.
16
+
17
+ This relies on the authenticated transport provided by the base client and is designed to be used only on
18
+ the composed ``HiveClient``.
19
+ """
20
+
21
+ def _get_core_items(
22
+ self,
23
+ endpoint: str,
24
+ item_type: type[CoreItemTypeT],
25
+ /,
26
+ extra_ctor_params: Optional[dict[str, Any]] = None,
27
+ **kwargs,
28
+ ) -> Iterable[CoreItemTypeT]:
29
+ """Yield typed items from a list endpoint with optional query parameters.
30
+
31
+ Handles both non-paginated list responses and DRF-style paginated
32
+ responses of the form:
33
+
34
+ {"count": N, "next": url | null, "previous": url | null, "results": [...]}
35
+ """
36
+ from ..client import HiveClient
37
+
38
+ assert isinstance(self, HiveClient), "self must be an instance of HiveClient"
39
+
40
+ if extra_ctor_params is None:
41
+ extra_ctor_params = {}
42
+
43
+ # Build query params, converting lists to comma-separated values
44
+ query_params = httpx.QueryParams()
45
+ for name, value in kwargs.items():
46
+ if value is None:
47
+ continue
48
+ if isinstance(value, list):
49
+ query_params = query_params.set(name, ",".join(str(x) for x in value))
50
+ else:
51
+ query_params = query_params.set(name, value)
52
+
53
+ data = self.get(endpoint, params=query_params)
54
+
55
+ # Non-paginated: assume the payload is the items list (or empty)
56
+ if not (isinstance(data, dict) and "results" in data):
57
+ items: list = data if isinstance(data, list) else []
58
+ yield from (
59
+ item_type.from_dict(x, **extra_ctor_params, hive_client=self)
60
+ for x in items
61
+ )
62
+
63
+ # Paginated: follow "next" links and yield all pages
64
+ def _paginate() -> Iterable[CoreItemTypeT]:
65
+ page = data
66
+ while True:
67
+ items = page.get("results", [])
68
+ for x in items:
69
+ yield item_type.from_dict(x, **extra_ctor_params, hive_client=self)
70
+ next_url = page.get("next")
71
+ if not next_url:
72
+ break
73
+ page = self.get(next_url)
74
+
75
+ return _paginate()
@@ -0,0 +1,82 @@
1
+ """
2
+ Exercise resource mixin for HiveClient.
3
+
4
+ Provides listing and retrieval of Exercise records, with rich filtering, via the Hive API.
5
+ Intended for mixing into the main HiveClient only.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, Iterable, Optional
9
+
10
+ from ..src.types.exercise import Exercise
11
+ from .client_shared import ClientCoreMixin
12
+ from .utils import resolve_item_or_id
13
+
14
+ if TYPE_CHECKING:
15
+ from ..src.types.module import ModuleLike
16
+ from ..src.types.subject import SubjectLike
17
+
18
+ class ExerciseClientMixin(ClientCoreMixin):
19
+ """
20
+ Mixin class providing exercise-related API methods for HiveClient.
21
+
22
+ Methods
23
+ -------
24
+ get_exercises(parent_module__id=None, parent_module=None, parent_subject=None, exercise_name=None, ...)
25
+ List all or filtered exercises via the Hive API. Supports advanced hierarchical filtering.
26
+ get_exercise(exercise_id)
27
+ Retrieve a single exercise by id.
28
+ """
29
+
30
+ # NOTE: Intended to be used only as part of the HiveClient composite class
31
+ def get_exercises( # pylint: disable=too-many-arguments
32
+ self,
33
+ *,
34
+ parent_module__id: Optional[int] = None,
35
+ parent_module__parent_subject__id: Optional[int] = None,
36
+ parent_module__parent_subject__parent_program__id__in: Optional[list[int]] = None,
37
+ queue__id: Optional[int] = None,
38
+ tags__id__in: Optional[list[int]] = None,
39
+ parent_module: Optional["ModuleLike"] = None,
40
+ parent_subject: Optional["SubjectLike"] = None,
41
+ exercise_name: Optional[str] = None,
42
+ ) -> Iterable[Exercise]:
43
+ """Yield ``Exercise`` objects, supporting rich parent-based filtering."""
44
+ from ..client import HiveClient
45
+
46
+ assert isinstance(self, HiveClient), "self must be an instance of HiveClient"
47
+ if parent_module is not None and parent_module__id is not None:
48
+ assert parent_module__id == resolve_item_or_id(parent_module)
49
+ parent_module__id = (
50
+ parent_module__id
51
+ if parent_module__id is not None
52
+ else resolve_item_or_id(parent_module)
53
+ )
54
+ if parent_subject is not None and parent_module__parent_subject__id is not None:
55
+ assert parent_module__parent_subject__id == resolve_item_or_id(parent_subject)
56
+ parent_module__parent_subject__id = (
57
+ parent_module__parent_subject__id
58
+ if parent_module__parent_subject__id is not None
59
+ else resolve_item_or_id(parent_subject)
60
+ )
61
+ exercises: Iterable[Exercise] = self._get_core_items(
62
+ "/api/core/course/exercises/",
63
+ Exercise,
64
+ parent_module__id=parent_module__id,
65
+ parent_module__parent_subject__id=parent_module__parent_subject__id,
66
+ parent_module__parent_subject__parent_program__id__in=parent_module__parent_subject__parent_program__id__in,
67
+ queue__id=queue__id,
68
+ tags__id__in=tags__id__in,
69
+ )
70
+ if exercise_name is not None:
71
+ exercises = filter(lambda e: e.name == exercise_name, exercises)
72
+ return exercises
73
+
74
+ def get_exercise(self, exercise_id: int) -> Exercise:
75
+ """Return a single ``Exercise`` by its id."""
76
+ from ..client import HiveClient
77
+
78
+ assert isinstance(self, HiveClient), "self must be an instance of HiveClient"
79
+ return Exercise.from_dict(
80
+ self.get(f"/api/core/course/exercises/{exercise_id}/"),
81
+ hive_client=self,
82
+ )
@@ -0,0 +1,41 @@
1
+ """Exercise form fields mixin for HiveClient.
2
+
3
+ Provides methods to list and retrieve form fields for a specific exercise.
4
+ """
5
+
6
+ from typing import Generator
7
+
8
+ from ..src.types.form_field import FormField
9
+ from .client_shared import ClientCoreMixin
10
+ from .utils import resolve_item_or_id
11
+
12
+
13
+ class FieldsClientMixin(ClientCoreMixin):
14
+ """Mixin that exposes form-field endpoints for exercises."""
15
+
16
+ def get_exercise_fields(
17
+ self,
18
+ exercise,
19
+ ) -> Generator[FormField, None, None]:
20
+ """Yield all form fields for the given ``exercise`` (id or instance)."""
21
+ exercise_id = resolve_item_or_id(exercise)
22
+ return self._get_core_items(
23
+ f"/api/core/course/exercises/{exercise_id}/fields/",
24
+ FormField,
25
+ exercise_id=exercise_id,
26
+ )
27
+
28
+ def get_exercise_field(
29
+ self,
30
+ exercise,
31
+ field_id: int,
32
+ ) -> FormField:
33
+ """Return a single form field for ``exercise`` by ``field_id``."""
34
+ from ..client import HiveClient
35
+
36
+ assert isinstance(self, HiveClient), "self must be an instance of HiveClient"
37
+ exercise_id = resolve_item_or_id(exercise)
38
+ return FormField.from_dict(
39
+ self.get(f"/api/core/course/exercises/{exercise_id}/fields/{field_id}/"),
40
+ hive_client=self,
41
+ )