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
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,30 @@
1
+ """PyHive CLI entry point"""
2
+
3
+ import typer
4
+ from pyhive.src._generated_versions import SUPPORTED_API_VERSIONS
5
+
6
+ app = typer.Typer(help="PyHive CLI")
7
+
8
+
9
+ @app.command()
10
+ def versions():
11
+ """Show supported Hive versions"""
12
+ print("Supported Hive Versions:")
13
+ for v in SUPPORTED_API_VERSIONS:
14
+ print(v)
15
+
16
+
17
+ @app.command()
18
+ def versions2():
19
+ """Show supported API versions"""
20
+ for v in SUPPORTED_API_VERSIONS:
21
+ print(v)
22
+
23
+
24
+ def main():
25
+ """PyHive CLI main entry point"""
26
+ app()
27
+
28
+
29
+ if __name__ == "__main__":
30
+ main()
pyhive/client.py ADDED
@@ -0,0 +1,570 @@
1
+ """High-level Hive API client.
2
+
3
+ This module provides ``HiveClient``, a small, synchronous authenticated
4
+ client for the Hive service. It exposes convenience methods that return
5
+ typed model objects from :mod:`src.types` and generator-based list
6
+ endpoints for memory-efficient iteration.
7
+ """
8
+
9
+ import re
10
+ from functools import lru_cache
11
+ from typing import TYPE_CHECKING, Any, Generator, Optional, Sequence, TypeVar
12
+
13
+ import httpx
14
+
15
+ from .src.authenticated_hive_client import _AuthenticatedHiveClient
16
+ from .src.types.assignment import Assignment
17
+ from .src.types.assignment_response import AssignmentResponse
18
+ from .src.types.class_ import Class
19
+ from .src.types.enums.class_type_enum import ClassTypeEnum
20
+ from .src.types.exercise import Exercise
21
+ from .src.types.form_field import FormField
22
+ from .src.types.module import Module
23
+ from .src.types.program import Program
24
+ from .src.types.queue import Queue
25
+ from .src.types.subject import Subject
26
+ from .src.types.user import User
27
+
28
+ if TYPE_CHECKING:
29
+ from .src.types.assignment import AssignmentLike
30
+ from .src.types.core_item import HiveCoreItem
31
+ from .src.types.exercise import ExerciseLike
32
+ from .src.types.module import ModuleLike
33
+ from .src.types.subject import SubjectLike
34
+
35
+ CoreItemTypeT = TypeVar("CoreItemTypeT", bound="HiveCoreItem")
36
+
37
+ ItemOrIdT = TypeVar("ItemOrIdT", bound="HiveCoreItem | int")
38
+
39
+
40
+ def resolve_item_or_id(
41
+ item_or_id: ItemOrIdT | None,
42
+ ) -> int | None:
43
+ """Resolve a HiveCoreItem or int to an int ID."""
44
+ from .src.types.core_item import (
45
+ HiveCoreItem,
46
+ ) # pylint: disable=import-outside-toplevel
47
+
48
+ if item_or_id is None:
49
+ return None
50
+ if not isinstance(item_or_id, (HiveCoreItem, int)):
51
+ raise TypeError(
52
+ f"Expected HiveCoreItem or int, got {type(item_or_id).__name__}"
53
+ )
54
+ return item_or_id.id if isinstance(item_or_id, HiveCoreItem) else item_or_id
55
+
56
+
57
+ class HiveClient(_AuthenticatedHiveClient): # pylint: disable=too-many-public-methods
58
+ """HTTP client for accessing Hive API.
59
+
60
+ The client is used as a context manager and provides typed helpers for
61
+ common Hive resources (programs, subjects, modules, exercises, users,
62
+ classes, and form fields).
63
+ """
64
+
65
+ def __enter__(self) -> "HiveClient":
66
+ """Enter context manager and return this client instance.
67
+
68
+ This delegates to the base class context manager which manages the
69
+ underlying :class:`httpx.Client` session.
70
+ """
71
+ super().__enter__()
72
+ return self
73
+
74
+ def get_programs(
75
+ self,
76
+ id__in: Optional[list[int]] = None,
77
+ program_name: Optional[str] = None,
78
+ ) -> Generator[Program, None, None]:
79
+ """Yield :class:`Program` objects.
80
+
81
+ Args:
82
+ id__in: Optional list of program ids to filter the results.
83
+
84
+ Yields:
85
+ Program instances parsed from the API response.
86
+ """
87
+
88
+ query_params = httpx.QueryParams()
89
+ if id__in is not None:
90
+ query_params.set("id__in", id__in)
91
+ programs = (
92
+ Program.from_dict(program_dict, hive_client=self)
93
+ for program_dict in super().get(
94
+ "/api/core/course/programs/",
95
+ params=query_params,
96
+ )
97
+ )
98
+ if program_name is not None:
99
+ programs = filter(lambda p: p.name == program_name, programs)
100
+ return programs
101
+
102
+ def get_program(self, program_id: int) -> Program:
103
+ """Return a single :class:`Program` by id.
104
+
105
+ Args:
106
+ program_id: The program identifier.
107
+
108
+ Returns:
109
+ A populated :class:`Program` object.
110
+ """
111
+ return Program.from_dict(
112
+ super().get(f"/api/core/course/programs/{program_id}/"),
113
+ hive_client=self,
114
+ )
115
+
116
+ def get_subjects(
117
+ self,
118
+ parent_program__id__in: Optional[list[int]] = None,
119
+ subject_name: Optional[str] = None,
120
+ ) -> Generator[Subject, None, None]:
121
+ """Yield :class:`Subject` objects for course subjects.
122
+
123
+ Args:
124
+ parent_program__id__in: Optional list of parent program ids to
125
+ filter subjects.
126
+
127
+ Yields:
128
+ Subject instances.
129
+ """
130
+
131
+ query_params = httpx.QueryParams()
132
+ if parent_program__id__in is not None:
133
+ query_params.set("parent_program__id__in", parent_program__id__in)
134
+ subjects = (
135
+ Subject.from_dict(subject_dict, hive_client=self)
136
+ for subject_dict in super().get(
137
+ "/api/core/course/subjects/",
138
+ params=query_params,
139
+ )
140
+ )
141
+
142
+ if subject_name is not None:
143
+ subjects = filter(lambda s: s.name == subject_name, subjects)
144
+
145
+ return subjects
146
+
147
+ def get_subject(self, subject_id: int) -> Subject:
148
+ """Return a single :class:`Subject` by id.
149
+
150
+ Args:
151
+ subject_id: The subject identifier.
152
+
153
+ Returns:
154
+ A populated :class:`Subject` object.
155
+ """
156
+ return Subject.from_dict(
157
+ super().get(f"/api/core/course/subjects/{subject_id}/"),
158
+ hive_client=self,
159
+ )
160
+
161
+ def get_modules(
162
+ self,
163
+ /,
164
+ parent_subject__id: Optional[int] = None,
165
+ parent_subject__parent_program__id__in: Optional[list[int]] = None,
166
+ parent_subject: Optional["SubjectLike"] = None,
167
+ module_name: Optional[str] = None,
168
+ ) -> Generator[Module, None, None]:
169
+ """Yield :class:`Module` objects for course modules.
170
+
171
+ Args:
172
+ parent_subject__id: Optional subject id to restrict modules.
173
+ parent_subject__parent_program__id__in: Optional list of program
174
+ ids to restrict modules.
175
+
176
+ Yields:
177
+ Module instances.
178
+ """
179
+
180
+ query_params = httpx.QueryParams()
181
+ if parent_subject__parent_program__id__in is not None:
182
+ query_params.set(
183
+ "parent_subject__parent_program__id__in",
184
+ parent_subject__parent_program__id__in,
185
+ )
186
+
187
+ parent_subject__id = (
188
+ parent_subject__id
189
+ if parent_subject__id is not None
190
+ else resolve_item_or_id(parent_subject)
191
+ )
192
+
193
+ if parent_subject__id is not None:
194
+ query_params.set("parent_subject__id", parent_subject__id)
195
+
196
+ modules = (
197
+ Module.from_dict(subject_dict, hive_client=self)
198
+ for subject_dict in super().get(
199
+ "/api/core/course/modules/",
200
+ params=query_params,
201
+ )
202
+ )
203
+ if module_name is not None:
204
+ modules = filter(lambda m: m.name == module_name, modules)
205
+ return modules
206
+
207
+ def get_module(self, module_id: int) -> Module:
208
+ """Return a single :class:`Module` by id.
209
+
210
+ Args:
211
+ module_id: The module identifier.
212
+
213
+ Returns:
214
+ A populated :class:`Module` object.
215
+ """
216
+ return Module.from_dict(
217
+ super().get(f"/api/core/course/modules/{module_id}/"),
218
+ hive_client=self,
219
+ )
220
+
221
+ def get_exercises( # pylint: disable=too-many-arguments
222
+ self,
223
+ *,
224
+ parent_module__id: Optional[int] = None,
225
+ parent_module__parent_subject__id: Optional[int] = None,
226
+ parent_module__parent_subject__parent_program__id__in: Optional[
227
+ list[int]
228
+ ] = None,
229
+ queue__id: Optional[int] = None,
230
+ tags__id__in: Optional[list[int]] = None,
231
+ parent_module: Optional["ModuleLike"] = None,
232
+ parent_subject: Optional["SubjectLike"] = None,
233
+ exercise_name: Optional[str] = None,
234
+ ) -> Generator[Exercise, None, None]:
235
+ """Yield :class:`Exercise` objects.
236
+
237
+ Accepts common filtering keyword args which are forwarded to the
238
+ underlying list endpoint.
239
+ """
240
+
241
+ if parent_module is not None and parent_module__id is not None:
242
+ assert parent_module__id == resolve_item_or_id(parent_module)
243
+ parent_module__id = (
244
+ parent_module__id
245
+ if parent_module__id is not None
246
+ else resolve_item_or_id(parent_module)
247
+ )
248
+
249
+ if parent_subject is not None and parent_module__parent_subject__id is not None:
250
+ assert parent_module__parent_subject__id == resolve_item_or_id(
251
+ parent_subject
252
+ )
253
+ parent_module__parent_subject__id = (
254
+ parent_module__parent_subject__id
255
+ if parent_module__parent_subject__id is not None
256
+ else resolve_item_or_id(parent_subject)
257
+ )
258
+
259
+ exercises = self._get_core_items(
260
+ "/api/core/course/exercises/",
261
+ Exercise,
262
+ parent_module__id=parent_module__id,
263
+ parent_module__parent_subject__id=parent_module__parent_subject__id,
264
+ parent_module__parent_subject__parent_program__id__in=parent_module__parent_subject__parent_program__id__in,
265
+ queue__id=queue__id,
266
+ tags__id__in=tags__id__in,
267
+ )
268
+ if exercise_name is not None:
269
+ exercises = filter(lambda e: e.name == exercise_name, exercises)
270
+ return exercises
271
+
272
+ def get_exercise(self, exercise_id: int) -> Exercise:
273
+ """Return a single :class:`Exercise` by id.
274
+
275
+ Args:
276
+ exercise_id: The exercise identifier.
277
+
278
+ Returns:
279
+ A populated :class:`Exercise` object.
280
+ """
281
+ return Exercise.from_dict(
282
+ super().get(f"/api/core/course/exercises/{exercise_id}/"),
283
+ hive_client=self,
284
+ )
285
+
286
+ def get_assignments( # pylint: disable=too-many-arguments
287
+ self,
288
+ *,
289
+ exercise__id: Optional[int] = None,
290
+ exercise__parent_module__id: Optional[int] = None,
291
+ exercise__parent_module__parent_subject__id: Optional[int] = None,
292
+ exercise__tags__id__in: Optional[Sequence[int]] = None,
293
+ queue__id: Optional[int] = None,
294
+ user__classes__id: Optional[int] = None,
295
+ user__classes__id__in: Optional[Sequence[int]] = None,
296
+ user__id__in: Optional[Sequence[int]] = None,
297
+ user__mentor__id: Optional[int] = None,
298
+ user__mentor__id__in: Optional[Sequence[int]] = None,
299
+ user__program__id__in: Optional[Sequence[int]] = None,
300
+ parent_module: Optional["ModuleLike"] = None,
301
+ parent_subject: Optional["SubjectLike"] = None,
302
+ ) -> Generator[Assignment, None, None]:
303
+ """Fetch assignments filtered by various optional parameters."""
304
+
305
+ if parent_module is not None and exercise__parent_module__id is not None:
306
+ assert exercise__parent_module__id == resolve_item_or_id(parent_module)
307
+ exercise__parent_module__id = (
308
+ exercise__parent_module__id
309
+ if exercise__parent_module__id is not None
310
+ else resolve_item_or_id(parent_module)
311
+ )
312
+
313
+ if (
314
+ parent_subject is not None
315
+ and exercise__parent_module__parent_subject__id is not None
316
+ ):
317
+ assert exercise__parent_module__parent_subject__id == resolve_item_or_id(
318
+ parent_subject
319
+ )
320
+ exercise__parent_module__parent_subject__id = (
321
+ exercise__parent_module__parent_subject__id
322
+ if exercise__parent_module__parent_subject__id is not None
323
+ else resolve_item_or_id(parent_subject)
324
+ )
325
+
326
+ return self._get_core_items(
327
+ "/api/core/assignments/",
328
+ Assignment,
329
+ exercise__id=exercise__id,
330
+ exercise__parent_module__id=exercise__parent_module__id,
331
+ exercise__parent_module__parent_subject__id=exercise__parent_module__parent_subject__id,
332
+ exercise__tags__id__in=exercise__tags__id__in,
333
+ queue__id=queue__id,
334
+ user__classes__id=user__classes__id,
335
+ user__classes__id__in=user__classes__id__in,
336
+ user__id__in=user__id__in,
337
+ user__mentor__id=user__mentor__id,
338
+ user__mentor__id__in=user__mentor__id__in,
339
+ user__program__id__in=user__program__id__in,
340
+ )
341
+
342
+ def get_assignment(self, assignment_id: int) -> Assignment:
343
+ """Return a single :class:`Assignment` by id.
344
+
345
+ Args:
346
+ assignment_id: The assignment identifier.
347
+
348
+ Returns:
349
+ A populated :class:`Assignment` object.
350
+ """
351
+ return Assignment.from_dict(
352
+ super().get(f"/api/core/assignments/{assignment_id}/"),
353
+ hive_client=self,
354
+ )
355
+
356
+ def get_users( # pylint: disable=too-many-arguments
357
+ self,
358
+ *,
359
+ classes__id__in: Optional[list[int]] = None,
360
+ clearance__in: Optional[list[int]] = None,
361
+ id__in: Optional[list[int]] = None,
362
+ mentor__id: Optional[int] = None,
363
+ mentor__id__in: Optional[list[int]] = None,
364
+ program__id__in: Optional[list[int]] = None,
365
+ program_checker__id__in: Optional[list[int]] = None,
366
+ ) -> Generator[User, None, None]:
367
+ """Yield :class:`User` objects from the management users endpoint.
368
+
369
+ All kwargs are optional filters forwarded to the API.
370
+ """
371
+
372
+ return self._get_core_items(
373
+ "/api/core/management/users/",
374
+ User,
375
+ classes__id__in=classes__id__in,
376
+ clearance__in=clearance__in,
377
+ id__in=id__in,
378
+ mentor__id=mentor__id,
379
+ mentor__id__in=mentor__id__in,
380
+ program__id__in=program__id__in,
381
+ program_checker__id__in=program_checker__id__in,
382
+ )
383
+
384
+ def get_user(self, user_id: int) -> User:
385
+ """Return a single :class:`User` by id.
386
+
387
+ Args:
388
+ user_id: The user identifier.
389
+
390
+ Returns:
391
+ A populated :class:`User` object.
392
+ """
393
+ return User.from_dict(
394
+ super().get(f"/api/core/management/users/{user_id}/"),
395
+ hive_client=self,
396
+ )
397
+
398
+ def get_user_me(self) -> User:
399
+ """Return the currently authenticated user.
400
+
401
+ Returns:
402
+ A populated :class:`User` object.
403
+ """
404
+ raise NotImplementedError("get_user_me() is not implemented")
405
+ # For some reason this endpoint does not return the same data as /users/{id}/
406
+ return User.from_dict( # pylint: disable=unreachable
407
+ super().get("/api/core/management/users/me/"),
408
+ hive_client=self,
409
+ )
410
+
411
+ def get_classes(
412
+ self,
413
+ *,
414
+ id__in: Optional[list[int]] = None,
415
+ name: Optional[str] = None,
416
+ program__id__in: Optional[list[int]] = None,
417
+ type_: Optional[ClassTypeEnum] = None,
418
+ ) -> Generator[Class, None, None]:
419
+ """Yield :class:`Class` objects from the management classes endpoint.
420
+
421
+ Filters may be provided as keyword arguments.
422
+ """
423
+
424
+ return self._get_core_items(
425
+ "/api/core/management/classes/",
426
+ Class,
427
+ id__in=id__in,
428
+ name=name,
429
+ program__id__in=program__id__in,
430
+ type_=type_,
431
+ )
432
+
433
+ def get_class(
434
+ self,
435
+ class_id: int,
436
+ ) -> Class:
437
+ """Return a single :class:`Class` by id.
438
+
439
+ Args:
440
+ class_id: The class identifier.
441
+
442
+ Returns:
443
+ A populated :class:`Class` object.
444
+ """
445
+ return Class.from_dict(
446
+ super().get(f"/api/core/management/classes/{class_id}/"),
447
+ hive_client=self,
448
+ )
449
+
450
+ @lru_cache(maxsize=256)
451
+ def get_exercise_fields(
452
+ self,
453
+ exercise: "ExerciseLike",
454
+ ) -> Generator[FormField, None, None]:
455
+ """Yield :class:`FormField` objects for an exercise.
456
+
457
+ Args:
458
+ exercise: The exercise identifier.
459
+ """
460
+
461
+ exercise_id = resolve_item_or_id(exercise)
462
+
463
+ return self._get_core_items(
464
+ f"/api/core/course/exercises/{exercise_id}/fields/",
465
+ FormField,
466
+ exercise_id=exercise_id,
467
+ )
468
+
469
+ @lru_cache(maxsize=1024)
470
+ def get_exercise_field(
471
+ self,
472
+ exercise: "ExerciseLike",
473
+ field_id: int,
474
+ ) -> FormField:
475
+ """Return a single :class:`FormField` for an exercise by id.
476
+
477
+ Args:
478
+ exercise_id: The exercise identifier.
479
+ field_id: The field identifier.
480
+
481
+ Returns:
482
+ A populated :class:`FormField` object.
483
+ """
484
+ exercise_id = resolve_item_or_id(exercise)
485
+ return FormField.from_dict(
486
+ super().get(f"/api/core/course/exercises/{exercise_id}/fields/{field_id}/"),
487
+ hive_client=self,
488
+ )
489
+
490
+ def get_assignment_responses(
491
+ self,
492
+ assignment: "AssignmentLike",
493
+ ) -> Generator[AssignmentResponse, None, None]:
494
+ """Get assignment responses for a given assignment."""
495
+ assignment_id = resolve_item_or_id(assignment)
496
+ return self._get_core_items(
497
+ f"/api/core/assignments/{assignment_id}/responses/",
498
+ AssignmentResponse,
499
+ assignment_id=assignment_id,
500
+ extra_ctor_params={"assignment_id": assignment_id},
501
+ )
502
+
503
+ def get_assignment_response(
504
+ self,
505
+ assignment: "AssignmentLike",
506
+ response_id: int,
507
+ ) -> AssignmentResponse:
508
+ """Return a single :class:`AssignmentResponse` by id."""
509
+ assignment_id = resolve_item_or_id(assignment)
510
+ return AssignmentResponse.from_dict(
511
+ super().get(
512
+ f"/api/core/assignments/{assignment_id}/responses/{response_id}/"
513
+ ),
514
+ assignment_id=assignment_id,
515
+ hive_client=self,
516
+ )
517
+
518
+ def get_queue(self, queue_id: int):
519
+ """Return a single :class:`Queue` by id.
520
+
521
+ Args:
522
+ queue_id: The queue identifier.
523
+
524
+ Returns:
525
+ A populated :class:`Queue` object.
526
+ """
527
+ return Queue.from_dict(
528
+ super().get(f"/api/core/queues/{queue_id}/"),
529
+ hive_client=self,
530
+ )
531
+
532
+ def _get_core_items(
533
+ self,
534
+ endpoint: str,
535
+ item_type: type[CoreItemTypeT],
536
+ /,
537
+ extra_ctor_params: Optional[dict[str, Any]] = None,
538
+ **kwargs: dict[str, Any], # noqa: ANN401
539
+ ) -> Generator[CoreItemTypeT, None, None]:
540
+ """Internal helper to yield typed core items from a list endpoint.
541
+
542
+ Args:
543
+ endpoint: API endpoint path for the list resource.
544
+ item_type: Model class with a ``from_dict`` constructor.
545
+ **kwargs: Filter query parameters forwarded to the endpoint.
546
+
547
+ Yields:
548
+ Instances of ``item_type`` created via ``from_dict``.
549
+ """
550
+ if extra_ctor_params is None:
551
+ extra_ctor_params = {}
552
+
553
+ query_params = httpx.QueryParams()
554
+ for name, value in kwargs.items():
555
+ if value is not None:
556
+ query_params = query_params.set(name, value)
557
+
558
+ return (
559
+ item_type.from_dict(x, **extra_ctor_params, hive_client=self)
560
+ for x in super().get(endpoint, params=query_params)
561
+ )
562
+
563
+ def get_hive_version(self) -> str:
564
+ """Return the Hive server version string."""
565
+ data = super().get("/api/core/schema/")
566
+
567
+ version = data.get("info", {}).get("version", "")
568
+ if not isinstance(version, str) or not re.match(r"^\d+\.\d+\.\d+", version):
569
+ raise ValueError("Invalid version string received from server")
570
+ return version
pyhive/src/__init__.py ADDED
File without changes
@@ -0,0 +1,3 @@
1
+ SUPPORTED_API_VERSIONS = ['5.1.2', '6.2.0']
2
+ MIN_API_VERSION = SUPPORTED_API_VERSIONS[0]
3
+ LATEST_API_VERSION = SUPPORTED_API_VERSIONS[-1]
@@ -0,0 +1,6 @@
1
+ """API versions of Hive supported by PyHive."""
2
+
3
+ from pyhive.src._generated_versions import SUPPORTED_API_VERSIONS
4
+
5
+ MIN_API_VERSION = SUPPORTED_API_VERSIONS[0]
6
+ LATEST_API_VERSION = SUPPORTED_API_VERSIONS[-1]