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.
- pyhive/__init__.py +13 -0
- pyhive/cli/__init__.py +0 -0
- pyhive/cli/main.py +31 -0
- pyhive/client/__init__.py +112 -0
- pyhive/client/assignment_responses.py +45 -0
- pyhive/client/assignments.py +115 -0
- pyhive/client/classes.py +108 -0
- pyhive/client/client_shared.py +75 -0
- pyhive/client/exercises.py +82 -0
- pyhive/client/fields.py +41 -0
- pyhive/client/help.py +191 -0
- pyhive/client/modules.py +84 -0
- pyhive/client/programs.py +116 -0
- pyhive/client/queues.py +21 -0
- pyhive/client/subjects.py +101 -0
- pyhive/client/users.py +307 -0
- pyhive/client/utils.py +42 -0
- pyhive/client/version.py +20 -0
- pyhive/src/__init__.py +0 -0
- pyhive/src/_generated_versions.py +3 -0
- pyhive/src/api_versions.py +6 -0
- pyhive/src/authenticated_hive_client.py +252 -0
- pyhive/src/types/__init__.py +0 -0
- pyhive/src/types/assignment.py +214 -0
- pyhive/src/types/assignment_response.py +205 -0
- pyhive/src/types/assignment_response_content.py +131 -0
- pyhive/src/types/autocheck_status.py +95 -0
- pyhive/src/types/class_.py +116 -0
- pyhive/src/types/common.py +56 -0
- pyhive/src/types/core_item.py +22 -0
- pyhive/src/types/enums/__init__.py +5 -0
- pyhive/src/types/enums/action_enum.py +18 -0
- pyhive/src/types/enums/assignment_response_type_enum.py +17 -0
- pyhive/src/types/enums/assignment_status_enum.py +17 -0
- pyhive/src/types/enums/class_type_enum.py +13 -0
- pyhive/src/types/enums/clearance_enum.py +15 -0
- pyhive/src/types/enums/event_type_enum.py +14 -0
- pyhive/src/types/enums/exercise_patbas_enum.py +15 -0
- pyhive/src/types/enums/exercise_preview_types.py +15 -0
- pyhive/src/types/enums/form_field_type_enum.py +15 -0
- pyhive/src/types/enums/gender_enum.py +14 -0
- pyhive/src/types/enums/help_response_type_enum.py +14 -0
- pyhive/src/types/enums/help_status_enum.py +13 -0
- pyhive/src/types/enums/help_type_enum.py +18 -0
- pyhive/src/types/enums/queue_rule_enum.py +15 -0
- pyhive/src/types/enums/status_enum.py +21 -0
- pyhive/src/types/enums/sync_status_enum.py +15 -0
- pyhive/src/types/enums/visibility_enum.py +14 -0
- pyhive/src/types/event.py +142 -0
- pyhive/src/types/event_attendees_type_0_item.py +69 -0
- pyhive/src/types/event_color.py +64 -0
- pyhive/src/types/exercise.py +217 -0
- pyhive/src/types/form_field.py +152 -0
- pyhive/src/types/help_.py +279 -0
- pyhive/src/types/help_response.py +113 -0
- pyhive/src/types/help_response_segel_nested.py +130 -0
- pyhive/src/types/module.py +141 -0
- pyhive/src/types/notification_nested.py +80 -0
- pyhive/src/types/program.py +186 -0
- pyhive/src/types/queue.py +152 -0
- pyhive/src/types/queue_item.py +87 -0
- pyhive/src/types/subject.py +163 -0
- pyhive/src/types/tag.py +63 -0
- pyhive/src/types/user.py +455 -0
- pyhive/types.py +31 -0
- pyhivelms-1.0.2.dist-info/METADATA +156 -0
- pyhivelms-1.0.2.dist-info/RECORD +69 -0
- pyhivelms-1.0.2.dist-info/WHEEL +4 -0
- 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
|
+
)
|
pyhive/client/classes.py
ADDED
|
@@ -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
|
+
)
|
pyhive/client/fields.py
ADDED
|
@@ -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
|
+
)
|