hackerrank 2026.7.19__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.
- hackerrank/__init__.py +1 -0
- hackerrank/_dict_types.py +349 -0
- hackerrank/async_client.py +2619 -0
- hackerrank/client.py +2623 -0
- hackerrank/exceptions.py +129 -0
- hackerrank/py.typed +0 -0
- hackerrank/transports.py +273 -0
- hackerrank/types.py +1086 -0
- hackerrank-2026.7.19.dist-info/METADATA +121 -0
- hackerrank-2026.7.19.dist-info/RECORD +15 -0
- hackerrank-2026.7.19.dist-info/WHEEL +5 -0
- hackerrank-2026.7.19.dist-info/licenses/LICENSE +21 -0
- hackerrank-2026.7.19.dist-info/scm_file_list.json +49 -0
- hackerrank-2026.7.19.dist-info/scm_version.json +8 -0
- hackerrank-2026.7.19.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,2619 @@
|
|
|
1
|
+
"""Async HackerRank for Work API client."""
|
|
2
|
+
|
|
3
|
+
import builtins
|
|
4
|
+
from collections.abc import Mapping, Sequence
|
|
5
|
+
from http import HTTPStatus
|
|
6
|
+
from types import TracebackType
|
|
7
|
+
from typing import Any, BinaryIO, Self
|
|
8
|
+
|
|
9
|
+
from beartype import beartype
|
|
10
|
+
|
|
11
|
+
from hackerrank.exceptions import HackerRankError
|
|
12
|
+
from hackerrank.transports import (
|
|
13
|
+
AsyncHTTPXTransport,
|
|
14
|
+
AsyncTransport,
|
|
15
|
+
TransportResponse,
|
|
16
|
+
)
|
|
17
|
+
from hackerrank.types import (
|
|
18
|
+
ATSCodePair,
|
|
19
|
+
ATSCodeScreen,
|
|
20
|
+
AuditLog,
|
|
21
|
+
Environment,
|
|
22
|
+
Interview,
|
|
23
|
+
InterviewTemplate,
|
|
24
|
+
InterviewTranscript,
|
|
25
|
+
Inviter,
|
|
26
|
+
JSONValue,
|
|
27
|
+
Page,
|
|
28
|
+
Question,
|
|
29
|
+
SCIMPage,
|
|
30
|
+
SCIMTeam,
|
|
31
|
+
SCIMUser,
|
|
32
|
+
Team,
|
|
33
|
+
Template,
|
|
34
|
+
Test,
|
|
35
|
+
TestCandidate,
|
|
36
|
+
User,
|
|
37
|
+
UserTeamMembership,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
_API_V3 = "/x/api/v3"
|
|
41
|
+
_SCIM_BASE_URL = "https://services.hackerrank.com/scim/v2"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _drop_none(
|
|
45
|
+
data: Mapping[str, JSONValue],
|
|
46
|
+
/,
|
|
47
|
+
) -> dict[str, JSONValue]:
|
|
48
|
+
"""Return a copy of ``data`` with ``None`` values removed.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
data: The dictionary to filter.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
A new dictionary without ``None`` values.
|
|
55
|
+
"""
|
|
56
|
+
return {k: v for k, v in data.items() if v is not None}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _coerce_int(value: object, /) -> int:
|
|
60
|
+
"""Coerce ``value`` to ``int``, defaulting to ``0``.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
value: A value that may be ``int``, ``str`` or
|
|
64
|
+
``None``.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
The integer value, or ``0`` if conversion is not
|
|
68
|
+
possible.
|
|
69
|
+
"""
|
|
70
|
+
if isinstance(value, bool):
|
|
71
|
+
return int(value)
|
|
72
|
+
if isinstance(value, int):
|
|
73
|
+
return value
|
|
74
|
+
if isinstance(value, str) and value:
|
|
75
|
+
try:
|
|
76
|
+
return int(value)
|
|
77
|
+
except ValueError:
|
|
78
|
+
return 0
|
|
79
|
+
return 0
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _coerce_str(value: object, /) -> str:
|
|
83
|
+
"""Coerce ``value`` to ``str``, defaulting to ``""``.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
value: A value that may be a ``str`` or ``None``.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
The string value, or ``""`` if ``None``.
|
|
90
|
+
"""
|
|
91
|
+
if isinstance(value, str):
|
|
92
|
+
return value
|
|
93
|
+
return ""
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _make_page[T](
|
|
97
|
+
items: list[T],
|
|
98
|
+
metadata: Mapping[str, JSONValue],
|
|
99
|
+
/,
|
|
100
|
+
) -> Page[T]:
|
|
101
|
+
"""Wrap ``items`` and ``metadata`` into a ``Page``.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
items: The items in the page.
|
|
105
|
+
metadata: The raw response metadata.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
A populated ``Page`` instance.
|
|
109
|
+
"""
|
|
110
|
+
return Page(
|
|
111
|
+
items,
|
|
112
|
+
page_total=_coerce_int(metadata.get("page_total")),
|
|
113
|
+
offset=_coerce_int(metadata.get("offset")),
|
|
114
|
+
previous=_coerce_str(metadata.get("previous")),
|
|
115
|
+
next_=_coerce_str(metadata.get("next")),
|
|
116
|
+
first=_coerce_str(metadata.get("first")),
|
|
117
|
+
last=_coerce_str(metadata.get("last")),
|
|
118
|
+
total=_coerce_int(metadata.get("total")),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _make_scim_page[T](
|
|
123
|
+
items: list[T],
|
|
124
|
+
payload: Mapping[str, JSONValue],
|
|
125
|
+
/,
|
|
126
|
+
) -> SCIMPage[T]:
|
|
127
|
+
"""Wrap items and a SCIM payload into a ``SCIMPage``.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
items: The SCIM items in the page.
|
|
131
|
+
payload: The raw SCIM response payload.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
A populated ``SCIMPage`` instance.
|
|
135
|
+
"""
|
|
136
|
+
schemas_raw = payload.get("schemas")
|
|
137
|
+
schemas: list[str] = [
|
|
138
|
+
item
|
|
139
|
+
for item in (schemas_raw if isinstance(schemas_raw, list) else [])
|
|
140
|
+
if isinstance(item, str)
|
|
141
|
+
]
|
|
142
|
+
start_index = _coerce_int(payload.get("startIndex")) or 1
|
|
143
|
+
return SCIMPage(
|
|
144
|
+
items,
|
|
145
|
+
schemas=schemas,
|
|
146
|
+
start_index=start_index,
|
|
147
|
+
items_per_page=_coerce_int(payload.get("itemsPerPage")),
|
|
148
|
+
total_results=_coerce_int(payload.get("totalResults")),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _list_params(
|
|
153
|
+
*,
|
|
154
|
+
limit: int | None,
|
|
155
|
+
offset: int | None,
|
|
156
|
+
extra: dict[str, str | int] | None = None,
|
|
157
|
+
) -> dict[str, str | int]:
|
|
158
|
+
"""Build query parameters for a list call.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
limit: Maximum number of items to return.
|
|
162
|
+
offset: Offset to start from.
|
|
163
|
+
extra: Any extra parameters to include.
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
The query parameter mapping.
|
|
167
|
+
"""
|
|
168
|
+
params: dict[str, str | int] = {}
|
|
169
|
+
if limit is not None:
|
|
170
|
+
params["limit"] = limit
|
|
171
|
+
if offset is not None:
|
|
172
|
+
params["offset"] = offset
|
|
173
|
+
if extra:
|
|
174
|
+
params.update(extra)
|
|
175
|
+
return params
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _question_body(
|
|
179
|
+
*,
|
|
180
|
+
name: str | None = None,
|
|
181
|
+
type: str | None = None, # noqa: A002 # pylint: disable=redefined-builtin
|
|
182
|
+
internal_notes: str | None = None,
|
|
183
|
+
languages: Sequence[str] | None = None,
|
|
184
|
+
problem_statement: str | None = None,
|
|
185
|
+
recommended_duration: int | None = None,
|
|
186
|
+
tags: Sequence[str] | None = None,
|
|
187
|
+
options: Sequence[str] | None = None,
|
|
188
|
+
answer: int | Sequence[int] | None = None,
|
|
189
|
+
score: float | None = None,
|
|
190
|
+
environment_id: int | None = None,
|
|
191
|
+
role_type: str | None = None,
|
|
192
|
+
scoring_command: str | None = None,
|
|
193
|
+
scoring_files: Sequence[str] | None = None,
|
|
194
|
+
readonly_paths: Sequence[str] | None = None,
|
|
195
|
+
default_files: Sequence[str] | None = None,
|
|
196
|
+
configuration: Mapping[str, JSONValue] | None = None,
|
|
197
|
+
testcases: Sequence[Mapping[str, JSONValue]] | None = None,
|
|
198
|
+
) -> dict[str, JSONValue]:
|
|
199
|
+
"""Build a JSON body for question create/update calls."""
|
|
200
|
+
return _drop_none(
|
|
201
|
+
{
|
|
202
|
+
"name": name,
|
|
203
|
+
"type": type,
|
|
204
|
+
"internal_notes": internal_notes,
|
|
205
|
+
"languages": (list(languages) if languages is not None else None),
|
|
206
|
+
"problem_statement": problem_statement,
|
|
207
|
+
"recommended_duration": recommended_duration,
|
|
208
|
+
"tags": list(tags) if tags is not None else None,
|
|
209
|
+
"options": (list(options) if options is not None else None),
|
|
210
|
+
"answer": (
|
|
211
|
+
list(answer)
|
|
212
|
+
if isinstance(answer, Sequence) and not isinstance(answer, str)
|
|
213
|
+
else answer
|
|
214
|
+
),
|
|
215
|
+
"score": score,
|
|
216
|
+
"environment_id": environment_id,
|
|
217
|
+
"role_type": role_type,
|
|
218
|
+
"scoring_command": scoring_command,
|
|
219
|
+
"scoring_files": (
|
|
220
|
+
list(scoring_files) if scoring_files is not None else None
|
|
221
|
+
),
|
|
222
|
+
"readonly_paths": (
|
|
223
|
+
list(readonly_paths) if readonly_paths is not None else None
|
|
224
|
+
),
|
|
225
|
+
"default_files": (
|
|
226
|
+
list(default_files) if default_files is not None else None
|
|
227
|
+
),
|
|
228
|
+
"configuration": configuration,
|
|
229
|
+
"testcases": (list(testcases) if testcases is not None else None),
|
|
230
|
+
},
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@beartype
|
|
235
|
+
class _AsyncNamespace:
|
|
236
|
+
"""Base class providing shared async request logic."""
|
|
237
|
+
|
|
238
|
+
def __init__(
|
|
239
|
+
self,
|
|
240
|
+
*,
|
|
241
|
+
transport: AsyncTransport,
|
|
242
|
+
base_url: str,
|
|
243
|
+
headers: dict[str, str],
|
|
244
|
+
) -> None:
|
|
245
|
+
"""Create a new async namespace.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
transport: The async HTTP transport.
|
|
249
|
+
base_url: The base URL for the API.
|
|
250
|
+
headers: Headers to send with every request.
|
|
251
|
+
"""
|
|
252
|
+
self.transport = transport
|
|
253
|
+
self.base_url = base_url
|
|
254
|
+
self.headers = headers
|
|
255
|
+
|
|
256
|
+
async def _request(
|
|
257
|
+
self,
|
|
258
|
+
*,
|
|
259
|
+
method: str,
|
|
260
|
+
url: str,
|
|
261
|
+
params: dict[str, str | int] | None = None,
|
|
262
|
+
json: Mapping[str, JSONValue] | None = None,
|
|
263
|
+
files: Mapping[str, Any] | None = None,
|
|
264
|
+
) -> TransportResponse:
|
|
265
|
+
"""Make an async HTTP request.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
method: The HTTP method.
|
|
269
|
+
url: The URL path.
|
|
270
|
+
params: Query parameters.
|
|
271
|
+
json: JSON-serialisable body.
|
|
272
|
+
files: Files to send as multipart form-data.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
The transport response.
|
|
276
|
+
|
|
277
|
+
Raises:
|
|
278
|
+
HackerRankError: If the response has an error
|
|
279
|
+
status code.
|
|
280
|
+
"""
|
|
281
|
+
response = await self.transport(
|
|
282
|
+
method=method,
|
|
283
|
+
url=self.base_url + url,
|
|
284
|
+
headers=self.headers,
|
|
285
|
+
params=params,
|
|
286
|
+
json=json,
|
|
287
|
+
files=files,
|
|
288
|
+
)
|
|
289
|
+
if response.status_code >= HTTPStatus.BAD_REQUEST:
|
|
290
|
+
raise HackerRankError.from_response(response=response)
|
|
291
|
+
return response
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
@beartype
|
|
295
|
+
class AsyncInterviewsNamespace(_AsyncNamespace):
|
|
296
|
+
"""Async namespace for interview operations."""
|
|
297
|
+
|
|
298
|
+
async def list(
|
|
299
|
+
self,
|
|
300
|
+
*,
|
|
301
|
+
limit: int | None = None,
|
|
302
|
+
offset: int | None = None,
|
|
303
|
+
) -> Page[Interview]:
|
|
304
|
+
"""List interviews.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
limit: Number of records to fetch.
|
|
308
|
+
offset: Offset of records.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
A page of interviews.
|
|
312
|
+
"""
|
|
313
|
+
response = await self._request(
|
|
314
|
+
method="GET",
|
|
315
|
+
url=f"{_API_V3}/interviews",
|
|
316
|
+
params=_list_params(limit=limit, offset=offset),
|
|
317
|
+
)
|
|
318
|
+
payload = response.json()
|
|
319
|
+
raw_items = list(payload.get("data", []))
|
|
320
|
+
items: list[Interview] = [
|
|
321
|
+
Interview.from_dict(data=item) for item in raw_items
|
|
322
|
+
]
|
|
323
|
+
return _make_page(items, payload)
|
|
324
|
+
|
|
325
|
+
async def create(
|
|
326
|
+
self,
|
|
327
|
+
*,
|
|
328
|
+
title: str | None = None,
|
|
329
|
+
from_: str | None = None,
|
|
330
|
+
to: str | None = None,
|
|
331
|
+
notes: str | None = None,
|
|
332
|
+
resume_url: str | None = None,
|
|
333
|
+
interviewers: Sequence[str] | None = None,
|
|
334
|
+
result_url: str | None = None,
|
|
335
|
+
candidate: Mapping[str, JSONValue] | None = None,
|
|
336
|
+
send_email: bool | None = None,
|
|
337
|
+
metadata: Mapping[str, JSONValue] | None = None,
|
|
338
|
+
interview_template_id: int | None = None,
|
|
339
|
+
) -> Interview:
|
|
340
|
+
"""Create an interview.
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
title: Title of the interview.
|
|
344
|
+
from_: Scheduled start time.
|
|
345
|
+
to: Scheduled end time.
|
|
346
|
+
notes: Private notes.
|
|
347
|
+
resume_url: URL to the candidate resume.
|
|
348
|
+
interviewers: Emails of interviewers.
|
|
349
|
+
result_url: URL invoked when the interview ends.
|
|
350
|
+
candidate: Candidate details.
|
|
351
|
+
send_email: Whether to send an email invite.
|
|
352
|
+
metadata: Arbitrary metadata.
|
|
353
|
+
interview_template_id: Template to apply.
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
The created interview.
|
|
357
|
+
"""
|
|
358
|
+
body = _drop_none(
|
|
359
|
+
{
|
|
360
|
+
"title": title,
|
|
361
|
+
"from": from_,
|
|
362
|
+
"to": to,
|
|
363
|
+
"notes": notes,
|
|
364
|
+
"resume_url": resume_url,
|
|
365
|
+
"interviewers": (
|
|
366
|
+
list(interviewers) if interviewers is not None else None
|
|
367
|
+
),
|
|
368
|
+
"result_url": result_url,
|
|
369
|
+
"candidate": candidate,
|
|
370
|
+
"send_email": send_email,
|
|
371
|
+
"metadata": metadata,
|
|
372
|
+
"interview_template_id": interview_template_id,
|
|
373
|
+
},
|
|
374
|
+
)
|
|
375
|
+
response = await self._request(
|
|
376
|
+
method="POST",
|
|
377
|
+
url=f"{_API_V3}/interviews",
|
|
378
|
+
json=body,
|
|
379
|
+
)
|
|
380
|
+
return Interview.from_dict(data=response.json())
|
|
381
|
+
|
|
382
|
+
async def get(self, *, interview_id: str) -> Interview:
|
|
383
|
+
"""Retrieve an interview.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
interview_id: The id of the interview.
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
The interview.
|
|
390
|
+
"""
|
|
391
|
+
response = await self._request(
|
|
392
|
+
method="GET",
|
|
393
|
+
url=f"{_API_V3}/interviews/{interview_id}",
|
|
394
|
+
)
|
|
395
|
+
return Interview.from_dict(data=response.json())
|
|
396
|
+
|
|
397
|
+
async def update(
|
|
398
|
+
self,
|
|
399
|
+
*,
|
|
400
|
+
interview_id: str,
|
|
401
|
+
title: str | None = None,
|
|
402
|
+
from_: str | None = None,
|
|
403
|
+
to: str | None = None,
|
|
404
|
+
notes: str | None = None,
|
|
405
|
+
resume_url: str | None = None,
|
|
406
|
+
result_url: str | None = None,
|
|
407
|
+
candidate: Mapping[str, JSONValue] | None = None,
|
|
408
|
+
send_email: bool | None = None,
|
|
409
|
+
metadata: Mapping[str, JSONValue] | None = None,
|
|
410
|
+
interview_template_id: int | None = None,
|
|
411
|
+
) -> None:
|
|
412
|
+
"""Update an interview.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
interview_id: The id of the interview.
|
|
416
|
+
title: New title.
|
|
417
|
+
from_: New start time.
|
|
418
|
+
to: New end time.
|
|
419
|
+
notes: New private notes.
|
|
420
|
+
resume_url: New resume URL.
|
|
421
|
+
result_url: New result URL.
|
|
422
|
+
candidate: New candidate details.
|
|
423
|
+
send_email: Whether to send an email.
|
|
424
|
+
metadata: New metadata.
|
|
425
|
+
interview_template_id: New template id.
|
|
426
|
+
"""
|
|
427
|
+
body = _drop_none(
|
|
428
|
+
{
|
|
429
|
+
"title": title,
|
|
430
|
+
"from": from_,
|
|
431
|
+
"to": to,
|
|
432
|
+
"notes": notes,
|
|
433
|
+
"resume_url": resume_url,
|
|
434
|
+
"result_url": result_url,
|
|
435
|
+
"candidate": candidate,
|
|
436
|
+
"send_email": send_email,
|
|
437
|
+
"metadata": metadata,
|
|
438
|
+
"interview_template_id": interview_template_id,
|
|
439
|
+
},
|
|
440
|
+
)
|
|
441
|
+
await self._request(
|
|
442
|
+
method="PUT",
|
|
443
|
+
url=f"{_API_V3}/interviews/{interview_id}",
|
|
444
|
+
json=body,
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
async def delete(self, *, interview_id: str) -> None:
|
|
448
|
+
"""Delete an interview.
|
|
449
|
+
|
|
450
|
+
Args:
|
|
451
|
+
interview_id: The id of the interview.
|
|
452
|
+
"""
|
|
453
|
+
await self._request(
|
|
454
|
+
method="DELETE",
|
|
455
|
+
url=f"{_API_V3}/interviews/{interview_id}",
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
async def get_transcript(
|
|
459
|
+
self,
|
|
460
|
+
*,
|
|
461
|
+
interview_id: str,
|
|
462
|
+
) -> InterviewTranscript:
|
|
463
|
+
"""Retrieve the transcript of an interview.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
interview_id: The id of the interview.
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
The transcript.
|
|
470
|
+
"""
|
|
471
|
+
response = await self._request(
|
|
472
|
+
method="GET",
|
|
473
|
+
url=(f"{_API_V3}/interviews/{interview_id}/transcript"),
|
|
474
|
+
)
|
|
475
|
+
return InterviewTranscript.from_dict(data=response.json())
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
@beartype
|
|
479
|
+
class AsyncInterviewTemplatesNamespace(_AsyncNamespace):
|
|
480
|
+
"""Async namespace for interview-template operations."""
|
|
481
|
+
|
|
482
|
+
async def list(
|
|
483
|
+
self,
|
|
484
|
+
*,
|
|
485
|
+
limit: int | None = None,
|
|
486
|
+
offset: int | None = None,
|
|
487
|
+
) -> Page[InterviewTemplate]:
|
|
488
|
+
"""List interview templates.
|
|
489
|
+
|
|
490
|
+
Args:
|
|
491
|
+
limit: Number of records to fetch.
|
|
492
|
+
offset: Offset of records.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
A page of interview templates.
|
|
496
|
+
"""
|
|
497
|
+
response = await self._request(
|
|
498
|
+
method="GET",
|
|
499
|
+
url=f"{_API_V3}/interview_templates",
|
|
500
|
+
params=_list_params(limit=limit, offset=offset),
|
|
501
|
+
)
|
|
502
|
+
payload = response.json()
|
|
503
|
+
raw_items = list(payload.get("data", []))
|
|
504
|
+
items: list[InterviewTemplate] = [
|
|
505
|
+
InterviewTemplate.from_dict(data=item) for item in raw_items
|
|
506
|
+
]
|
|
507
|
+
return _make_page(items, payload)
|
|
508
|
+
|
|
509
|
+
async def create(
|
|
510
|
+
self,
|
|
511
|
+
*,
|
|
512
|
+
name: str,
|
|
513
|
+
roles: Sequence[str] | None = None,
|
|
514
|
+
team_share: int | None = None,
|
|
515
|
+
questions: Sequence[str] | None = None,
|
|
516
|
+
scorecard: int | None = None,
|
|
517
|
+
) -> InterviewTemplate:
|
|
518
|
+
"""Create an interview template.
|
|
519
|
+
|
|
520
|
+
Args:
|
|
521
|
+
name: The template name.
|
|
522
|
+
roles: Roles for the template.
|
|
523
|
+
team_share: Team-share flag.
|
|
524
|
+
questions: Associated question ids.
|
|
525
|
+
scorecard: Scorecard id.
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
The created interview template.
|
|
529
|
+
"""
|
|
530
|
+
body = _drop_none(
|
|
531
|
+
{
|
|
532
|
+
"name": name,
|
|
533
|
+
"roles": list(roles) if roles is not None else None,
|
|
534
|
+
"team_share": team_share,
|
|
535
|
+
"questions": (
|
|
536
|
+
list(questions) if questions is not None else None
|
|
537
|
+
),
|
|
538
|
+
"scorecard": scorecard,
|
|
539
|
+
},
|
|
540
|
+
)
|
|
541
|
+
response = await self._request(
|
|
542
|
+
method="POST",
|
|
543
|
+
url=f"{_API_V3}/interview_templates",
|
|
544
|
+
json=body,
|
|
545
|
+
)
|
|
546
|
+
return InterviewTemplate.from_dict(data=response.json())
|
|
547
|
+
|
|
548
|
+
async def get(
|
|
549
|
+
self,
|
|
550
|
+
*,
|
|
551
|
+
template_id: int | str,
|
|
552
|
+
) -> InterviewTemplate:
|
|
553
|
+
"""Retrieve an interview template.
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
template_id: The id of the template.
|
|
557
|
+
|
|
558
|
+
Returns:
|
|
559
|
+
The template.
|
|
560
|
+
"""
|
|
561
|
+
response = await self._request(
|
|
562
|
+
method="GET",
|
|
563
|
+
url=(f"{_API_V3}/interview_templates/{template_id}"),
|
|
564
|
+
)
|
|
565
|
+
return InterviewTemplate.from_dict(data=response.json())
|
|
566
|
+
|
|
567
|
+
async def update(
|
|
568
|
+
self,
|
|
569
|
+
*,
|
|
570
|
+
template_id: int | str,
|
|
571
|
+
name: str | None = None,
|
|
572
|
+
roles: Sequence[str] | None = None,
|
|
573
|
+
team_share: int | None = None,
|
|
574
|
+
questions: Sequence[str] | None = None,
|
|
575
|
+
scorecard: int | None = None,
|
|
576
|
+
) -> None:
|
|
577
|
+
"""Update an interview template.
|
|
578
|
+
|
|
579
|
+
Args:
|
|
580
|
+
template_id: The id of the template.
|
|
581
|
+
name: New name.
|
|
582
|
+
roles: New roles.
|
|
583
|
+
team_share: New team-share flag.
|
|
584
|
+
questions: New question ids.
|
|
585
|
+
scorecard: New scorecard id.
|
|
586
|
+
"""
|
|
587
|
+
body = _drop_none(
|
|
588
|
+
{
|
|
589
|
+
"name": name,
|
|
590
|
+
"roles": list(roles) if roles is not None else None,
|
|
591
|
+
"team_share": team_share,
|
|
592
|
+
"questions": (
|
|
593
|
+
list(questions) if questions is not None else None
|
|
594
|
+
),
|
|
595
|
+
"scorecard": scorecard,
|
|
596
|
+
},
|
|
597
|
+
)
|
|
598
|
+
await self._request(
|
|
599
|
+
method="PUT",
|
|
600
|
+
url=(f"{_API_V3}/interview_templates/{template_id}"),
|
|
601
|
+
json=body,
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
async def delete(self, *, template_id: int | str) -> None:
|
|
605
|
+
"""Delete an interview template.
|
|
606
|
+
|
|
607
|
+
Args:
|
|
608
|
+
template_id: The id of the template.
|
|
609
|
+
"""
|
|
610
|
+
await self._request(
|
|
611
|
+
method="DELETE",
|
|
612
|
+
url=(f"{_API_V3}/interview_templates/{template_id}"),
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
@beartype
|
|
617
|
+
class AsyncEnvironmentsNamespace(_AsyncNamespace):
|
|
618
|
+
"""Async namespace for project-question environment operations."""
|
|
619
|
+
|
|
620
|
+
async def list(self) -> builtins.list[Environment]:
|
|
621
|
+
"""List project-question environments.
|
|
622
|
+
|
|
623
|
+
Returns:
|
|
624
|
+
The available environments.
|
|
625
|
+
"""
|
|
626
|
+
response = await self._request(
|
|
627
|
+
method="GET",
|
|
628
|
+
url=f"{_API_V3}/environments",
|
|
629
|
+
)
|
|
630
|
+
raw_items = list(response.json().get("environments", []))
|
|
631
|
+
return [Environment.from_dict(data=item) for item in raw_items]
|
|
632
|
+
|
|
633
|
+
async def get(self, *, environment_id: int) -> Environment:
|
|
634
|
+
"""Retrieve a project-question environment.
|
|
635
|
+
|
|
636
|
+
Args:
|
|
637
|
+
environment_id: The id of the environment.
|
|
638
|
+
|
|
639
|
+
Returns:
|
|
640
|
+
The environment.
|
|
641
|
+
"""
|
|
642
|
+
response = await self._request(
|
|
643
|
+
method="GET",
|
|
644
|
+
url=f"{_API_V3}/environments/{environment_id}",
|
|
645
|
+
)
|
|
646
|
+
return Environment.from_dict(data=response.json()["environment"])
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
@beartype
|
|
650
|
+
class AsyncQuestionsNamespace(_AsyncNamespace):
|
|
651
|
+
"""Async namespace for question operations."""
|
|
652
|
+
|
|
653
|
+
async def list(
|
|
654
|
+
self,
|
|
655
|
+
*,
|
|
656
|
+
limit: int | None = None,
|
|
657
|
+
offset: int | None = None,
|
|
658
|
+
) -> Page[Question]:
|
|
659
|
+
"""List questions.
|
|
660
|
+
|
|
661
|
+
Args:
|
|
662
|
+
limit: Number of records to fetch.
|
|
663
|
+
offset: Offset of records.
|
|
664
|
+
|
|
665
|
+
Returns:
|
|
666
|
+
A page of questions.
|
|
667
|
+
"""
|
|
668
|
+
response = await self._request(
|
|
669
|
+
method="GET",
|
|
670
|
+
url=f"{_API_V3}/questions",
|
|
671
|
+
params=_list_params(limit=limit, offset=offset),
|
|
672
|
+
)
|
|
673
|
+
payload = response.json()
|
|
674
|
+
raw_items = list(payload.get("data", []))
|
|
675
|
+
items: list[Question] = [
|
|
676
|
+
Question.from_dict(data=item) for item in raw_items
|
|
677
|
+
]
|
|
678
|
+
return _make_page(items, payload)
|
|
679
|
+
|
|
680
|
+
async def get(self, *, question_id: str) -> Question:
|
|
681
|
+
"""Retrieve a question.
|
|
682
|
+
|
|
683
|
+
Args:
|
|
684
|
+
question_id: The id of the question.
|
|
685
|
+
|
|
686
|
+
Returns:
|
|
687
|
+
The question.
|
|
688
|
+
"""
|
|
689
|
+
response = await self._request(
|
|
690
|
+
method="GET",
|
|
691
|
+
url=f"{_API_V3}/questions/{question_id}",
|
|
692
|
+
)
|
|
693
|
+
return Question.from_dict(data=response.json())
|
|
694
|
+
|
|
695
|
+
async def create(
|
|
696
|
+
self,
|
|
697
|
+
*,
|
|
698
|
+
name: str,
|
|
699
|
+
type: str, # noqa: A002 # pylint: disable=redefined-builtin
|
|
700
|
+
internal_notes: str | None = None,
|
|
701
|
+
languages: Sequence[str] | None = None,
|
|
702
|
+
problem_statement: str | None = None,
|
|
703
|
+
recommended_duration: int | None = None,
|
|
704
|
+
tags: Sequence[str] | None = None,
|
|
705
|
+
options: Sequence[str] | None = None,
|
|
706
|
+
answer: int | Sequence[int] | None = None,
|
|
707
|
+
score: float | None = None,
|
|
708
|
+
environment_id: int | None = None,
|
|
709
|
+
role_type: str | None = None,
|
|
710
|
+
scoring_command: str | None = None,
|
|
711
|
+
scoring_files: Sequence[str] | None = None,
|
|
712
|
+
readonly_paths: Sequence[str] | None = None,
|
|
713
|
+
default_files: Sequence[str] | None = None,
|
|
714
|
+
configuration: Mapping[str, JSONValue] | None = None,
|
|
715
|
+
testcases: Sequence[Mapping[str, JSONValue]] | None = None,
|
|
716
|
+
) -> Question:
|
|
717
|
+
"""Create a question.
|
|
718
|
+
|
|
719
|
+
Args:
|
|
720
|
+
name: Question name.
|
|
721
|
+
type: Question type (``code``, ``mcq``, ...).
|
|
722
|
+
internal_notes: Private notes.
|
|
723
|
+
languages: Supported languages.
|
|
724
|
+
problem_statement: Problem statement.
|
|
725
|
+
recommended_duration: Recommended duration.
|
|
726
|
+
tags: Tags.
|
|
727
|
+
options: MCQ options.
|
|
728
|
+
answer: Correct MCQ answer.
|
|
729
|
+
score: Maximum score for project questions.
|
|
730
|
+
environment_id: Environment id for a fullstack question.
|
|
731
|
+
role_type: Role type for a fullstack question.
|
|
732
|
+
scoring_command: Command used to score the submission.
|
|
733
|
+
scoring_files: Files that hold scoring tests.
|
|
734
|
+
readonly_paths: Paths the candidate cannot edit.
|
|
735
|
+
default_files: File paths opened by default.
|
|
736
|
+
configuration: Project configuration.
|
|
737
|
+
testcases: Project scoring test cases.
|
|
738
|
+
|
|
739
|
+
Returns:
|
|
740
|
+
The created question.
|
|
741
|
+
"""
|
|
742
|
+
response = await self._request(
|
|
743
|
+
method="POST",
|
|
744
|
+
url=f"{_API_V3}/questions",
|
|
745
|
+
json=_question_body(
|
|
746
|
+
name=name,
|
|
747
|
+
type=type,
|
|
748
|
+
internal_notes=internal_notes,
|
|
749
|
+
languages=languages,
|
|
750
|
+
problem_statement=problem_statement,
|
|
751
|
+
recommended_duration=recommended_duration,
|
|
752
|
+
tags=tags,
|
|
753
|
+
options=options,
|
|
754
|
+
answer=answer,
|
|
755
|
+
score=score,
|
|
756
|
+
environment_id=environment_id,
|
|
757
|
+
role_type=role_type,
|
|
758
|
+
scoring_command=scoring_command,
|
|
759
|
+
scoring_files=scoring_files,
|
|
760
|
+
readonly_paths=readonly_paths,
|
|
761
|
+
default_files=default_files,
|
|
762
|
+
configuration=configuration,
|
|
763
|
+
testcases=testcases,
|
|
764
|
+
),
|
|
765
|
+
)
|
|
766
|
+
return Question.from_dict(data=response.json())
|
|
767
|
+
|
|
768
|
+
async def update(
|
|
769
|
+
self,
|
|
770
|
+
*,
|
|
771
|
+
question_id: str,
|
|
772
|
+
name: str | None = None,
|
|
773
|
+
# pylint: disable-next=redefined-builtin
|
|
774
|
+
type: str | None = None, # noqa: A002
|
|
775
|
+
internal_notes: str | None = None,
|
|
776
|
+
languages: Sequence[str] | None = None,
|
|
777
|
+
problem_statement: str | None = None,
|
|
778
|
+
recommended_duration: int | None = None,
|
|
779
|
+
tags: Sequence[str] | None = None,
|
|
780
|
+
options: Sequence[str] | None = None,
|
|
781
|
+
answer: int | Sequence[int] | None = None,
|
|
782
|
+
score: float | None = None,
|
|
783
|
+
environment_id: int | None = None,
|
|
784
|
+
role_type: str | None = None,
|
|
785
|
+
scoring_command: str | None = None,
|
|
786
|
+
scoring_files: Sequence[str] | None = None,
|
|
787
|
+
readonly_paths: Sequence[str] | None = None,
|
|
788
|
+
default_files: Sequence[str] | None = None,
|
|
789
|
+
configuration: Mapping[str, JSONValue] | None = None,
|
|
790
|
+
testcases: Sequence[Mapping[str, JSONValue]] | None = None,
|
|
791
|
+
) -> Question | None:
|
|
792
|
+
"""Update a question.
|
|
793
|
+
|
|
794
|
+
Args:
|
|
795
|
+
question_id: The id of the question.
|
|
796
|
+
name: New name.
|
|
797
|
+
type: New question type.
|
|
798
|
+
internal_notes: New internal notes.
|
|
799
|
+
languages: New supported languages.
|
|
800
|
+
problem_statement: New problem statement.
|
|
801
|
+
recommended_duration: New recommended duration.
|
|
802
|
+
tags: New tags.
|
|
803
|
+
options: New MCQ options.
|
|
804
|
+
answer: New MCQ answer.
|
|
805
|
+
score: New maximum score.
|
|
806
|
+
environment_id: New environment id.
|
|
807
|
+
role_type: New role type.
|
|
808
|
+
scoring_command: New scoring command.
|
|
809
|
+
scoring_files: New scoring files.
|
|
810
|
+
readonly_paths: New read-only paths.
|
|
811
|
+
default_files: New default file paths.
|
|
812
|
+
configuration: Project configuration to merge.
|
|
813
|
+
testcases: New project scoring test cases.
|
|
814
|
+
|
|
815
|
+
Returns:
|
|
816
|
+
The updated question when returned by the API, otherwise
|
|
817
|
+
``None``.
|
|
818
|
+
"""
|
|
819
|
+
response = await self._request(
|
|
820
|
+
method="PUT",
|
|
821
|
+
url=f"{_API_V3}/questions/{question_id}",
|
|
822
|
+
json=_question_body(
|
|
823
|
+
name=name,
|
|
824
|
+
type=type,
|
|
825
|
+
internal_notes=internal_notes,
|
|
826
|
+
languages=languages,
|
|
827
|
+
problem_statement=problem_statement,
|
|
828
|
+
recommended_duration=recommended_duration,
|
|
829
|
+
tags=tags,
|
|
830
|
+
options=options,
|
|
831
|
+
answer=answer,
|
|
832
|
+
score=score,
|
|
833
|
+
environment_id=environment_id,
|
|
834
|
+
role_type=role_type,
|
|
835
|
+
scoring_command=scoring_command,
|
|
836
|
+
scoring_files=scoring_files,
|
|
837
|
+
readonly_paths=readonly_paths,
|
|
838
|
+
default_files=default_files,
|
|
839
|
+
configuration=configuration,
|
|
840
|
+
testcases=testcases,
|
|
841
|
+
),
|
|
842
|
+
)
|
|
843
|
+
if response.content:
|
|
844
|
+
return Question.from_dict(data=response.json())
|
|
845
|
+
return None
|
|
846
|
+
|
|
847
|
+
async def upload_project_zip(
|
|
848
|
+
self,
|
|
849
|
+
*,
|
|
850
|
+
question_id: str,
|
|
851
|
+
file: bytes | BinaryIO,
|
|
852
|
+
filename: str = "project.zip",
|
|
853
|
+
content_type: str = "application/zip",
|
|
854
|
+
) -> dict[str, JSONValue]:
|
|
855
|
+
"""Upload a project zip for a fullstack question.
|
|
856
|
+
|
|
857
|
+
Args:
|
|
858
|
+
question_id: The id of the question.
|
|
859
|
+
file: The zip file content or a binary file object.
|
|
860
|
+
filename: The multipart filename.
|
|
861
|
+
content_type: The multipart content type.
|
|
862
|
+
|
|
863
|
+
Returns:
|
|
864
|
+
The raw API response, including ``file_url`` and
|
|
865
|
+
``file_path``.
|
|
866
|
+
"""
|
|
867
|
+
response = await self._request(
|
|
868
|
+
method="POST",
|
|
869
|
+
url=f"{_API_V3}/questions/{question_id}/upload_project_zip",
|
|
870
|
+
files={"file": (filename, file, content_type)},
|
|
871
|
+
)
|
|
872
|
+
result: dict[str, JSONValue] = dict(response.json())
|
|
873
|
+
return result
|
|
874
|
+
|
|
875
|
+
async def update_codestubs(
|
|
876
|
+
self,
|
|
877
|
+
*,
|
|
878
|
+
question_id: str,
|
|
879
|
+
codestubs: Mapping[str, JSONValue],
|
|
880
|
+
) -> None:
|
|
881
|
+
"""Update custom code-stubs for a question.
|
|
882
|
+
|
|
883
|
+
Args:
|
|
884
|
+
question_id: The id of the question.
|
|
885
|
+
codestubs: A mapping describing the code-stubs.
|
|
886
|
+
"""
|
|
887
|
+
await self._request(
|
|
888
|
+
method="PUT",
|
|
889
|
+
url=(f"{_API_V3}/questions/{question_id}/custom_codestubs"),
|
|
890
|
+
json=codestubs,
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
async def generate_codestubs(
|
|
894
|
+
self,
|
|
895
|
+
*,
|
|
896
|
+
question_id: str,
|
|
897
|
+
body: Mapping[str, JSONValue] | None = None,
|
|
898
|
+
) -> dict[str, JSONValue]:
|
|
899
|
+
"""Generate code-stubs for a question.
|
|
900
|
+
|
|
901
|
+
Args:
|
|
902
|
+
question_id: The id of the question.
|
|
903
|
+
body: An optional request body.
|
|
904
|
+
|
|
905
|
+
Returns:
|
|
906
|
+
The raw API response.
|
|
907
|
+
"""
|
|
908
|
+
response = await self._request(
|
|
909
|
+
method="PUT",
|
|
910
|
+
url=(f"{_API_V3}/questions/{question_id}/generate"),
|
|
911
|
+
json=body if body is not None else {},
|
|
912
|
+
)
|
|
913
|
+
result: dict[str, JSONValue] = dict(response.json())
|
|
914
|
+
return result
|
|
915
|
+
|
|
916
|
+
async def add_testcase(
|
|
917
|
+
self,
|
|
918
|
+
*,
|
|
919
|
+
question_id: str,
|
|
920
|
+
body: Mapping[str, JSONValue],
|
|
921
|
+
) -> dict[str, JSONValue]:
|
|
922
|
+
"""Add a test case to a question.
|
|
923
|
+
|
|
924
|
+
Args:
|
|
925
|
+
question_id: The id of the question.
|
|
926
|
+
body: The test-case payload.
|
|
927
|
+
|
|
928
|
+
Returns:
|
|
929
|
+
The raw API response.
|
|
930
|
+
"""
|
|
931
|
+
response = await self._request(
|
|
932
|
+
method="POST",
|
|
933
|
+
url=(f"{_API_V3}/questions/{question_id}/testcases"),
|
|
934
|
+
json=body,
|
|
935
|
+
)
|
|
936
|
+
result: dict[str, JSONValue] = dict(response.json())
|
|
937
|
+
return result
|
|
938
|
+
|
|
939
|
+
async def update_testcase(
|
|
940
|
+
self,
|
|
941
|
+
*,
|
|
942
|
+
question_id: str,
|
|
943
|
+
testcase_id: str,
|
|
944
|
+
body: Mapping[str, JSONValue],
|
|
945
|
+
) -> None:
|
|
946
|
+
"""Update an existing test case.
|
|
947
|
+
|
|
948
|
+
Args:
|
|
949
|
+
question_id: The id of the question.
|
|
950
|
+
testcase_id: The id of the test case.
|
|
951
|
+
body: The test-case payload.
|
|
952
|
+
"""
|
|
953
|
+
await self._request(
|
|
954
|
+
method="PUT",
|
|
955
|
+
url=(f"{_API_V3}/questions/{question_id}/testcases/{testcase_id}"),
|
|
956
|
+
json=body,
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
async def delete_testcase(
|
|
960
|
+
self,
|
|
961
|
+
*,
|
|
962
|
+
question_id: str,
|
|
963
|
+
testcase_id: str,
|
|
964
|
+
) -> None:
|
|
965
|
+
"""Delete a single test case.
|
|
966
|
+
|
|
967
|
+
Args:
|
|
968
|
+
question_id: The id of the question.
|
|
969
|
+
testcase_id: The id of the test case.
|
|
970
|
+
"""
|
|
971
|
+
await self._request(
|
|
972
|
+
method="DELETE",
|
|
973
|
+
url=(f"{_API_V3}/questions/{question_id}/testcases/{testcase_id}"),
|
|
974
|
+
)
|
|
975
|
+
|
|
976
|
+
async def delete_all_testcases(self, *, question_id: str) -> None:
|
|
977
|
+
"""Delete every test case on a question.
|
|
978
|
+
|
|
979
|
+
Args:
|
|
980
|
+
question_id: The id of the question.
|
|
981
|
+
"""
|
|
982
|
+
await self._request(
|
|
983
|
+
method="DELETE",
|
|
984
|
+
url=(f"{_API_V3}/questions/{question_id}/testcases/delete_all"),
|
|
985
|
+
)
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
@beartype
|
|
989
|
+
class AsyncTestCandidatesNamespace(_AsyncNamespace):
|
|
990
|
+
"""Async namespace for candidates of a test."""
|
|
991
|
+
|
|
992
|
+
async def list(
|
|
993
|
+
self,
|
|
994
|
+
*,
|
|
995
|
+
test_id: str,
|
|
996
|
+
limit: int | None = None,
|
|
997
|
+
offset: int | None = None,
|
|
998
|
+
) -> Page[TestCandidate]:
|
|
999
|
+
"""List candidates for a test.
|
|
1000
|
+
|
|
1001
|
+
Args:
|
|
1002
|
+
test_id: The id of the test.
|
|
1003
|
+
limit: Number of records to fetch.
|
|
1004
|
+
offset: Offset of records.
|
|
1005
|
+
|
|
1006
|
+
Returns:
|
|
1007
|
+
A page of candidates.
|
|
1008
|
+
"""
|
|
1009
|
+
response = await self._request(
|
|
1010
|
+
method="GET",
|
|
1011
|
+
url=f"{_API_V3}/tests/{test_id}/candidates",
|
|
1012
|
+
params=_list_params(limit=limit, offset=offset),
|
|
1013
|
+
)
|
|
1014
|
+
payload = response.json()
|
|
1015
|
+
raw_items = list(payload.get("data", []))
|
|
1016
|
+
items: list[TestCandidate] = [
|
|
1017
|
+
TestCandidate.from_dict(data=item) for item in raw_items
|
|
1018
|
+
]
|
|
1019
|
+
return _make_page(items, payload)
|
|
1020
|
+
|
|
1021
|
+
async def search(
|
|
1022
|
+
self,
|
|
1023
|
+
*,
|
|
1024
|
+
test_id: str,
|
|
1025
|
+
search: str,
|
|
1026
|
+
limit: int | None = None,
|
|
1027
|
+
offset: int | None = None,
|
|
1028
|
+
) -> Page[TestCandidate]:
|
|
1029
|
+
"""Search candidates within a test.
|
|
1030
|
+
|
|
1031
|
+
Args:
|
|
1032
|
+
test_id: The id of the test.
|
|
1033
|
+
search: Search query.
|
|
1034
|
+
limit: Number of records to fetch.
|
|
1035
|
+
offset: Offset of records.
|
|
1036
|
+
|
|
1037
|
+
Returns:
|
|
1038
|
+
A page of candidates.
|
|
1039
|
+
"""
|
|
1040
|
+
params = _list_params(
|
|
1041
|
+
limit=limit,
|
|
1042
|
+
offset=offset,
|
|
1043
|
+
extra={"search": search},
|
|
1044
|
+
)
|
|
1045
|
+
response = await self._request(
|
|
1046
|
+
method="GET",
|
|
1047
|
+
url=f"{_API_V3}/tests/{test_id}/candidates/search",
|
|
1048
|
+
params=params,
|
|
1049
|
+
)
|
|
1050
|
+
payload = response.json()
|
|
1051
|
+
raw_items = list(payload.get("data", []))
|
|
1052
|
+
items: list[TestCandidate] = [
|
|
1053
|
+
TestCandidate.from_dict(data=item) for item in raw_items
|
|
1054
|
+
]
|
|
1055
|
+
return _make_page(items, payload)
|
|
1056
|
+
|
|
1057
|
+
async def invite(
|
|
1058
|
+
self,
|
|
1059
|
+
*,
|
|
1060
|
+
test_id: str,
|
|
1061
|
+
email: str,
|
|
1062
|
+
full_name: str | None = None,
|
|
1063
|
+
send_email: bool | None = None,
|
|
1064
|
+
evaluator_email: str | None = None,
|
|
1065
|
+
test_result_url: str | None = None,
|
|
1066
|
+
test_finish_url: str | None = None,
|
|
1067
|
+
tags: Sequence[str] | None = None,
|
|
1068
|
+
invite_valid_from: str | None = None,
|
|
1069
|
+
invite_valid_to: str | None = None,
|
|
1070
|
+
force: bool | None = None,
|
|
1071
|
+
force_reattempt: bool | None = None,
|
|
1072
|
+
accommodations: Mapping[str, JSONValue] | None = None,
|
|
1073
|
+
invite_metadata: Mapping[str, JSONValue] | None = None,
|
|
1074
|
+
webhook_authentication: Mapping[str, JSONValue] | None = None,
|
|
1075
|
+
accept_result_updates: bool | None = None,
|
|
1076
|
+
subject: str | None = None,
|
|
1077
|
+
message: str | None = None,
|
|
1078
|
+
template: str | None = None,
|
|
1079
|
+
) -> TestCandidate:
|
|
1080
|
+
"""Invite a candidate to a test.
|
|
1081
|
+
|
|
1082
|
+
Args:
|
|
1083
|
+
test_id: The id of the test.
|
|
1084
|
+
email: Candidate email address.
|
|
1085
|
+
full_name: Candidate full name.
|
|
1086
|
+
send_email: Whether to send the invitation email.
|
|
1087
|
+
evaluator_email: Evaluator email.
|
|
1088
|
+
test_result_url: URL to which results are posted.
|
|
1089
|
+
test_finish_url: URL the candidate sees on finish.
|
|
1090
|
+
tags: Tags to set on the candidate.
|
|
1091
|
+
invite_valid_from: Invitation start time.
|
|
1092
|
+
invite_valid_to: Invitation end time.
|
|
1093
|
+
force: Force inviting even if previously invited.
|
|
1094
|
+
force_reattempt: Allow re-attempt.
|
|
1095
|
+
accommodations: Accommodation settings.
|
|
1096
|
+
invite_metadata: Arbitrary metadata.
|
|
1097
|
+
webhook_authentication: Webhook auth config.
|
|
1098
|
+
accept_result_updates: Accept result updates flag.
|
|
1099
|
+
subject: Custom email subject.
|
|
1100
|
+
message: Custom email message.
|
|
1101
|
+
template: Email template id.
|
|
1102
|
+
|
|
1103
|
+
Returns:
|
|
1104
|
+
The created candidate.
|
|
1105
|
+
"""
|
|
1106
|
+
body = _drop_none(
|
|
1107
|
+
{
|
|
1108
|
+
"email": email,
|
|
1109
|
+
"full_name": full_name,
|
|
1110
|
+
"send_email": send_email,
|
|
1111
|
+
"evaluator_email": evaluator_email,
|
|
1112
|
+
"test_result_url": test_result_url,
|
|
1113
|
+
"test_finish_url": test_finish_url,
|
|
1114
|
+
"tags": list(tags) if tags is not None else None,
|
|
1115
|
+
"invite_valid_from": invite_valid_from,
|
|
1116
|
+
"invite_valid_to": invite_valid_to,
|
|
1117
|
+
"force": force,
|
|
1118
|
+
"force_reattempt": force_reattempt,
|
|
1119
|
+
"accommodations": accommodations,
|
|
1120
|
+
"invite_metadata": invite_metadata,
|
|
1121
|
+
"webhook_authentication": webhook_authentication,
|
|
1122
|
+
"accept_result_updates": accept_result_updates,
|
|
1123
|
+
"subject": subject,
|
|
1124
|
+
"message": message,
|
|
1125
|
+
"template": template,
|
|
1126
|
+
},
|
|
1127
|
+
)
|
|
1128
|
+
response = await self._request(
|
|
1129
|
+
method="POST",
|
|
1130
|
+
url=f"{_API_V3}/tests/{test_id}/candidates",
|
|
1131
|
+
json=body,
|
|
1132
|
+
)
|
|
1133
|
+
return TestCandidate.from_dict(data=response.json())
|
|
1134
|
+
|
|
1135
|
+
async def get(
|
|
1136
|
+
self,
|
|
1137
|
+
*,
|
|
1138
|
+
test_id: str,
|
|
1139
|
+
candidate_id: str,
|
|
1140
|
+
additional_fields: str | None = None,
|
|
1141
|
+
) -> TestCandidate:
|
|
1142
|
+
"""Retrieve a single candidate.
|
|
1143
|
+
|
|
1144
|
+
Args:
|
|
1145
|
+
test_id: The id of the test.
|
|
1146
|
+
candidate_id: The id of the candidate.
|
|
1147
|
+
additional_fields: Comma-separated extra fields.
|
|
1148
|
+
|
|
1149
|
+
Returns:
|
|
1150
|
+
The candidate.
|
|
1151
|
+
"""
|
|
1152
|
+
params: dict[str, str | int] = {}
|
|
1153
|
+
if additional_fields is not None:
|
|
1154
|
+
params["additional_fields"] = additional_fields
|
|
1155
|
+
response = await self._request(
|
|
1156
|
+
method="GET",
|
|
1157
|
+
url=(f"{_API_V3}/tests/{test_id}/candidates/{candidate_id}"),
|
|
1158
|
+
params=params or None,
|
|
1159
|
+
)
|
|
1160
|
+
return TestCandidate.from_dict(data=response.json())
|
|
1161
|
+
|
|
1162
|
+
async def update(
|
|
1163
|
+
self,
|
|
1164
|
+
*,
|
|
1165
|
+
test_id: str,
|
|
1166
|
+
candidate_id: str,
|
|
1167
|
+
full_name: str | None = None,
|
|
1168
|
+
ats_state: int | None = None,
|
|
1169
|
+
invite_valid_from: str | None = None,
|
|
1170
|
+
invite_valid_to: str | None = None,
|
|
1171
|
+
invite_metadata: Mapping[str, JSONValue] | None = None,
|
|
1172
|
+
evaluator_email: str | None = None,
|
|
1173
|
+
test_finish_url: str | None = None,
|
|
1174
|
+
test_result_url: str | None = None,
|
|
1175
|
+
webhook_authentication: Mapping[str, JSONValue] | None = None,
|
|
1176
|
+
accept_result_updates: bool | None = None,
|
|
1177
|
+
tags: Sequence[str] | None = None,
|
|
1178
|
+
accommodations: Mapping[str, JSONValue] | None = None,
|
|
1179
|
+
) -> None:
|
|
1180
|
+
"""Update a candidate.
|
|
1181
|
+
|
|
1182
|
+
Args:
|
|
1183
|
+
test_id: The id of the test.
|
|
1184
|
+
candidate_id: The id of the candidate.
|
|
1185
|
+
full_name: New full name.
|
|
1186
|
+
ats_state: New ATS state.
|
|
1187
|
+
invite_valid_from: New invitation start time.
|
|
1188
|
+
invite_valid_to: New invitation end time.
|
|
1189
|
+
invite_metadata: New invite metadata.
|
|
1190
|
+
evaluator_email: New evaluator email.
|
|
1191
|
+
test_finish_url: New finish URL.
|
|
1192
|
+
test_result_url: New result URL.
|
|
1193
|
+
webhook_authentication: New webhook auth.
|
|
1194
|
+
accept_result_updates: New flag value.
|
|
1195
|
+
tags: New tags.
|
|
1196
|
+
accommodations: New accommodations.
|
|
1197
|
+
"""
|
|
1198
|
+
body = _drop_none(
|
|
1199
|
+
{
|
|
1200
|
+
"full_name": full_name,
|
|
1201
|
+
"ats_state": ats_state,
|
|
1202
|
+
"invite_valid_from": invite_valid_from,
|
|
1203
|
+
"invite_valid_to": invite_valid_to,
|
|
1204
|
+
"invite_metadata": invite_metadata,
|
|
1205
|
+
"evaluator_email": evaluator_email,
|
|
1206
|
+
"test_finish_url": test_finish_url,
|
|
1207
|
+
"test_result_url": test_result_url,
|
|
1208
|
+
"webhook_authentication": webhook_authentication,
|
|
1209
|
+
"accept_result_updates": accept_result_updates,
|
|
1210
|
+
"tags": list(tags) if tags is not None else None,
|
|
1211
|
+
"accommodations": accommodations,
|
|
1212
|
+
},
|
|
1213
|
+
)
|
|
1214
|
+
await self._request(
|
|
1215
|
+
method="PUT",
|
|
1216
|
+
url=(f"{_API_V3}/tests/{test_id}/candidates/{candidate_id}"),
|
|
1217
|
+
json=body,
|
|
1218
|
+
)
|
|
1219
|
+
|
|
1220
|
+
async def cancel_invite(
|
|
1221
|
+
self,
|
|
1222
|
+
*,
|
|
1223
|
+
test_id: str,
|
|
1224
|
+
candidate_id: str,
|
|
1225
|
+
) -> None:
|
|
1226
|
+
"""Cancel a candidate's invitation.
|
|
1227
|
+
|
|
1228
|
+
Args:
|
|
1229
|
+
test_id: The id of the test.
|
|
1230
|
+
candidate_id: The id of the candidate.
|
|
1231
|
+
"""
|
|
1232
|
+
await self._request(
|
|
1233
|
+
method="DELETE",
|
|
1234
|
+
url=(
|
|
1235
|
+
f"{_API_V3}/tests/{test_id}/candidates/{candidate_id}/invite"
|
|
1236
|
+
),
|
|
1237
|
+
)
|
|
1238
|
+
|
|
1239
|
+
async def delete_report(
|
|
1240
|
+
self,
|
|
1241
|
+
*,
|
|
1242
|
+
test_id: str,
|
|
1243
|
+
candidate_id: str,
|
|
1244
|
+
) -> None:
|
|
1245
|
+
"""Delete the report for a candidate.
|
|
1246
|
+
|
|
1247
|
+
Args:
|
|
1248
|
+
test_id: The id of the test.
|
|
1249
|
+
candidate_id: The id of the candidate.
|
|
1250
|
+
"""
|
|
1251
|
+
await self._request(
|
|
1252
|
+
method="DELETE",
|
|
1253
|
+
url=(
|
|
1254
|
+
f"{_API_V3}/tests/{test_id}/candidates/{candidate_id}/report"
|
|
1255
|
+
),
|
|
1256
|
+
)
|
|
1257
|
+
|
|
1258
|
+
async def get_report_pdf(
|
|
1259
|
+
self,
|
|
1260
|
+
*,
|
|
1261
|
+
test_id: str,
|
|
1262
|
+
candidate_id: str,
|
|
1263
|
+
format_: str = "url",
|
|
1264
|
+
) -> dict[str, JSONValue]:
|
|
1265
|
+
"""Retrieve the PDF report for a candidate.
|
|
1266
|
+
|
|
1267
|
+
Args:
|
|
1268
|
+
test_id: The id of the test.
|
|
1269
|
+
candidate_id: The id of the candidate.
|
|
1270
|
+
format_: Format of the PDF (e.g. ``"url"``).
|
|
1271
|
+
|
|
1272
|
+
Returns:
|
|
1273
|
+
The raw API response. When ``format_`` is
|
|
1274
|
+
``"url"`` the response typically contains a URL
|
|
1275
|
+
for downloading the PDF.
|
|
1276
|
+
"""
|
|
1277
|
+
response = await self._request(
|
|
1278
|
+
method="GET",
|
|
1279
|
+
url=(f"{_API_V3}/tests/{test_id}/candidates/{candidate_id}/pdf"),
|
|
1280
|
+
params={"format": format_},
|
|
1281
|
+
)
|
|
1282
|
+
result: dict[str, JSONValue] = dict(response.json())
|
|
1283
|
+
return result
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
@beartype
|
|
1287
|
+
class AsyncTestsNamespace(_AsyncNamespace):
|
|
1288
|
+
"""Async namespace for test operations."""
|
|
1289
|
+
|
|
1290
|
+
def __init__(
|
|
1291
|
+
self,
|
|
1292
|
+
*,
|
|
1293
|
+
transport: AsyncTransport,
|
|
1294
|
+
base_url: str,
|
|
1295
|
+
headers: dict[str, str],
|
|
1296
|
+
) -> None:
|
|
1297
|
+
"""Create the namespace.
|
|
1298
|
+
|
|
1299
|
+
Args:
|
|
1300
|
+
transport: The HTTP transport.
|
|
1301
|
+
base_url: The base URL for the API.
|
|
1302
|
+
headers: Headers to send with every request.
|
|
1303
|
+
"""
|
|
1304
|
+
super().__init__(
|
|
1305
|
+
transport=transport,
|
|
1306
|
+
base_url=base_url,
|
|
1307
|
+
headers=headers,
|
|
1308
|
+
)
|
|
1309
|
+
self.candidates: AsyncTestCandidatesNamespace = (
|
|
1310
|
+
AsyncTestCandidatesNamespace(
|
|
1311
|
+
transport=transport,
|
|
1312
|
+
base_url=base_url,
|
|
1313
|
+
headers=headers,
|
|
1314
|
+
)
|
|
1315
|
+
)
|
|
1316
|
+
|
|
1317
|
+
async def list(
|
|
1318
|
+
self,
|
|
1319
|
+
*,
|
|
1320
|
+
limit: int | None = None,
|
|
1321
|
+
offset: int | None = None,
|
|
1322
|
+
) -> Page[Test]:
|
|
1323
|
+
"""List tests.
|
|
1324
|
+
|
|
1325
|
+
Args:
|
|
1326
|
+
limit: Number of records to fetch.
|
|
1327
|
+
offset: Offset of records.
|
|
1328
|
+
|
|
1329
|
+
Returns:
|
|
1330
|
+
A page of tests.
|
|
1331
|
+
"""
|
|
1332
|
+
response = await self._request(
|
|
1333
|
+
method="GET",
|
|
1334
|
+
url=f"{_API_V3}/tests",
|
|
1335
|
+
params=_list_params(limit=limit, offset=offset),
|
|
1336
|
+
)
|
|
1337
|
+
payload = response.json()
|
|
1338
|
+
raw_items = list(payload.get("data", []))
|
|
1339
|
+
items: list[Test] = [Test.from_dict(data=item) for item in raw_items]
|
|
1340
|
+
return _make_page(items, payload)
|
|
1341
|
+
|
|
1342
|
+
async def create(
|
|
1343
|
+
self,
|
|
1344
|
+
*,
|
|
1345
|
+
name: str,
|
|
1346
|
+
starttime: str | None = None,
|
|
1347
|
+
endtime: str | None = None,
|
|
1348
|
+
duration: int | None = None,
|
|
1349
|
+
instructions: str | None = None,
|
|
1350
|
+
locked: bool | None = None,
|
|
1351
|
+
draft: bool | None = None,
|
|
1352
|
+
languages: Sequence[str] | None = None,
|
|
1353
|
+
candidate_details: Sequence[str] | None = None,
|
|
1354
|
+
custom_acknowledge_text: str | None = None,
|
|
1355
|
+
cutoff_score: int | None = None,
|
|
1356
|
+
master_password: str | None = None,
|
|
1357
|
+
hide_compile_test: bool | None = None,
|
|
1358
|
+
tags: Sequence[str] | None = None,
|
|
1359
|
+
role_ids: Sequence[str] | None = None,
|
|
1360
|
+
experience: Sequence[str] | None = None,
|
|
1361
|
+
questions: Sequence[str] | None = None,
|
|
1362
|
+
mcq_incorrect_score: int | None = None,
|
|
1363
|
+
mcq_correct_score: int | None = None,
|
|
1364
|
+
shuffle_questions: bool | None = None,
|
|
1365
|
+
test_admins: Sequence[str] | None = None,
|
|
1366
|
+
hide_template: bool | None = None,
|
|
1367
|
+
enable_acknowledgement: bool | None = None,
|
|
1368
|
+
enable_proctoring: bool | None = None,
|
|
1369
|
+
enable_advanced_proctoring: bool | None = None,
|
|
1370
|
+
enable_secure_assessment_mode: bool | None = None,
|
|
1371
|
+
enable_ml_plagiarism_analysis: bool | None = None,
|
|
1372
|
+
enable_photo_identification: bool | None = None,
|
|
1373
|
+
ide_config: str | None = None,
|
|
1374
|
+
) -> Test:
|
|
1375
|
+
"""Create a test.
|
|
1376
|
+
|
|
1377
|
+
Args:
|
|
1378
|
+
name: The name of the test.
|
|
1379
|
+
starttime: Test start time.
|
|
1380
|
+
endtime: Test end time.
|
|
1381
|
+
duration: Test duration in minutes.
|
|
1382
|
+
instructions: Test instructions.
|
|
1383
|
+
locked: Whether the test is locked.
|
|
1384
|
+
draft: Whether the test is a draft.
|
|
1385
|
+
languages: Allowed programming languages.
|
|
1386
|
+
candidate_details: Candidate detail fields.
|
|
1387
|
+
custom_acknowledge_text: Custom acknowledge text.
|
|
1388
|
+
cutoff_score: The cut-off score.
|
|
1389
|
+
master_password: Master password for the test.
|
|
1390
|
+
hide_compile_test: Hide compile-test option.
|
|
1391
|
+
tags: Tags applied to the test.
|
|
1392
|
+
role_ids: Role ids associated with the test.
|
|
1393
|
+
experience: Experience-level metadata.
|
|
1394
|
+
questions: Question ids to include.
|
|
1395
|
+
mcq_incorrect_score: Score for incorrect MCQ.
|
|
1396
|
+
mcq_correct_score: Score for correct MCQ.
|
|
1397
|
+
shuffle_questions: Whether to shuffle.
|
|
1398
|
+
test_admins: Test admin user ids.
|
|
1399
|
+
hide_template: Hide the template flag.
|
|
1400
|
+
enable_acknowledgement: Enable acknowledgement.
|
|
1401
|
+
enable_proctoring: Enable proctoring.
|
|
1402
|
+
enable_advanced_proctoring: Advanced proctoring.
|
|
1403
|
+
enable_secure_assessment_mode: Secure mode flag.
|
|
1404
|
+
enable_ml_plagiarism_analysis: ML plagiarism.
|
|
1405
|
+
enable_photo_identification: Photo ID flag.
|
|
1406
|
+
ide_config: IDE configuration.
|
|
1407
|
+
|
|
1408
|
+
Returns:
|
|
1409
|
+
The created test.
|
|
1410
|
+
"""
|
|
1411
|
+
body = _drop_none(
|
|
1412
|
+
{
|
|
1413
|
+
"name": name,
|
|
1414
|
+
"starttime": starttime,
|
|
1415
|
+
"endtime": endtime,
|
|
1416
|
+
"duration": duration,
|
|
1417
|
+
"instructions": instructions,
|
|
1418
|
+
"locked": locked,
|
|
1419
|
+
"draft": draft,
|
|
1420
|
+
"languages": (
|
|
1421
|
+
list(languages) if languages is not None else None
|
|
1422
|
+
),
|
|
1423
|
+
"candidate_details": (
|
|
1424
|
+
list(candidate_details)
|
|
1425
|
+
if candidate_details is not None
|
|
1426
|
+
else None
|
|
1427
|
+
),
|
|
1428
|
+
"custom_acknowledge_text": custom_acknowledge_text,
|
|
1429
|
+
"cutoff_score": cutoff_score,
|
|
1430
|
+
"master_password": master_password,
|
|
1431
|
+
"hide_compile_test": hide_compile_test,
|
|
1432
|
+
"tags": list(tags) if tags is not None else None,
|
|
1433
|
+
"role_ids": (list(role_ids) if role_ids is not None else None),
|
|
1434
|
+
"experience": (
|
|
1435
|
+
list(experience) if experience is not None else None
|
|
1436
|
+
),
|
|
1437
|
+
"questions": (
|
|
1438
|
+
list(questions) if questions is not None else None
|
|
1439
|
+
),
|
|
1440
|
+
"mcq_incorrect_score": mcq_incorrect_score,
|
|
1441
|
+
"mcq_correct_score": mcq_correct_score,
|
|
1442
|
+
"shuffle_questions": shuffle_questions,
|
|
1443
|
+
"test_admins": (
|
|
1444
|
+
list(test_admins) if test_admins is not None else None
|
|
1445
|
+
),
|
|
1446
|
+
"hide_template": hide_template,
|
|
1447
|
+
"enable_acknowledgement": enable_acknowledgement,
|
|
1448
|
+
"enable_proctoring": enable_proctoring,
|
|
1449
|
+
"enable_advanced_proctoring": (enable_advanced_proctoring),
|
|
1450
|
+
"enable_secure_assessment_mode": (
|
|
1451
|
+
enable_secure_assessment_mode
|
|
1452
|
+
),
|
|
1453
|
+
"enable_ml_plagiarism_analysis": (
|
|
1454
|
+
enable_ml_plagiarism_analysis
|
|
1455
|
+
),
|
|
1456
|
+
"enable_photo_identification": (enable_photo_identification),
|
|
1457
|
+
"ide_config": ide_config,
|
|
1458
|
+
},
|
|
1459
|
+
)
|
|
1460
|
+
response = await self._request(
|
|
1461
|
+
method="POST",
|
|
1462
|
+
url=f"{_API_V3}/tests",
|
|
1463
|
+
json=body,
|
|
1464
|
+
)
|
|
1465
|
+
return Test.from_dict(data=response.json())
|
|
1466
|
+
|
|
1467
|
+
async def get(
|
|
1468
|
+
self,
|
|
1469
|
+
*,
|
|
1470
|
+
test_id: str,
|
|
1471
|
+
additional_fields: str | None = None,
|
|
1472
|
+
) -> Test:
|
|
1473
|
+
"""Retrieve a test.
|
|
1474
|
+
|
|
1475
|
+
Args:
|
|
1476
|
+
test_id: The id of the test.
|
|
1477
|
+
additional_fields: Comma-separated extra fields.
|
|
1478
|
+
|
|
1479
|
+
Returns:
|
|
1480
|
+
The test.
|
|
1481
|
+
"""
|
|
1482
|
+
params: dict[str, str | int] = {}
|
|
1483
|
+
if additional_fields is not None:
|
|
1484
|
+
params["additional_fields"] = additional_fields
|
|
1485
|
+
response = await self._request(
|
|
1486
|
+
method="GET",
|
|
1487
|
+
url=f"{_API_V3}/tests/{test_id}",
|
|
1488
|
+
params=params or None,
|
|
1489
|
+
)
|
|
1490
|
+
return Test.from_dict(data=response.json())
|
|
1491
|
+
|
|
1492
|
+
async def update(
|
|
1493
|
+
self,
|
|
1494
|
+
*,
|
|
1495
|
+
test_id: str,
|
|
1496
|
+
body: Mapping[str, JSONValue],
|
|
1497
|
+
) -> None:
|
|
1498
|
+
"""Update a test using a raw payload.
|
|
1499
|
+
|
|
1500
|
+
Args:
|
|
1501
|
+
test_id: The id of the test.
|
|
1502
|
+
body: A mapping of fields to update. See the
|
|
1503
|
+
HackerRank API documentation for the
|
|
1504
|
+
supported fields.
|
|
1505
|
+
"""
|
|
1506
|
+
await self._request(
|
|
1507
|
+
method="PUT",
|
|
1508
|
+
url=f"{_API_V3}/tests/{test_id}",
|
|
1509
|
+
json=body,
|
|
1510
|
+
)
|
|
1511
|
+
|
|
1512
|
+
async def delete(self, *, test_id: str) -> None:
|
|
1513
|
+
"""Delete a test.
|
|
1514
|
+
|
|
1515
|
+
Args:
|
|
1516
|
+
test_id: The id of the test.
|
|
1517
|
+
"""
|
|
1518
|
+
await self._request(
|
|
1519
|
+
method="DELETE",
|
|
1520
|
+
url=f"{_API_V3}/tests/{test_id}",
|
|
1521
|
+
)
|
|
1522
|
+
|
|
1523
|
+
async def archive(self, *, test_id: str) -> None:
|
|
1524
|
+
"""Archive a test.
|
|
1525
|
+
|
|
1526
|
+
Args:
|
|
1527
|
+
test_id: The id of the test.
|
|
1528
|
+
"""
|
|
1529
|
+
await self._request(
|
|
1530
|
+
method="POST",
|
|
1531
|
+
url=f"{_API_V3}/tests/{test_id}/archive",
|
|
1532
|
+
)
|
|
1533
|
+
|
|
1534
|
+
async def list_inviters(
|
|
1535
|
+
self,
|
|
1536
|
+
*,
|
|
1537
|
+
test_id: str,
|
|
1538
|
+
limit: int | None = None,
|
|
1539
|
+
offset: int | None = None,
|
|
1540
|
+
) -> Page[Inviter]:
|
|
1541
|
+
"""List inviters for a test.
|
|
1542
|
+
|
|
1543
|
+
Args:
|
|
1544
|
+
test_id: The id of the test.
|
|
1545
|
+
limit: Number of records to fetch.
|
|
1546
|
+
offset: Offset of records.
|
|
1547
|
+
|
|
1548
|
+
Returns:
|
|
1549
|
+
A page of inviters.
|
|
1550
|
+
"""
|
|
1551
|
+
response = await self._request(
|
|
1552
|
+
method="GET",
|
|
1553
|
+
url=f"{_API_V3}/tests/{test_id}/inviters",
|
|
1554
|
+
params=_list_params(limit=limit, offset=offset),
|
|
1555
|
+
)
|
|
1556
|
+
payload = response.json()
|
|
1557
|
+
raw_items = list(payload.get("data", []))
|
|
1558
|
+
items: list[Inviter] = [
|
|
1559
|
+
Inviter.from_dict(data=item) for item in raw_items
|
|
1560
|
+
]
|
|
1561
|
+
return _make_page(items, payload)
|
|
1562
|
+
|
|
1563
|
+
|
|
1564
|
+
@beartype
|
|
1565
|
+
class AsyncTemplatesNamespace(_AsyncNamespace):
|
|
1566
|
+
"""Async namespace for invite-email templates."""
|
|
1567
|
+
|
|
1568
|
+
async def list(
|
|
1569
|
+
self,
|
|
1570
|
+
*,
|
|
1571
|
+
limit: int | None = None,
|
|
1572
|
+
offset: int | None = None,
|
|
1573
|
+
) -> Page[Template]:
|
|
1574
|
+
"""List invite templates.
|
|
1575
|
+
|
|
1576
|
+
Args:
|
|
1577
|
+
limit: Number of records to fetch.
|
|
1578
|
+
offset: Offset of records.
|
|
1579
|
+
|
|
1580
|
+
Returns:
|
|
1581
|
+
A page of templates.
|
|
1582
|
+
"""
|
|
1583
|
+
response = await self._request(
|
|
1584
|
+
method="GET",
|
|
1585
|
+
url=f"{_API_V3}/templates",
|
|
1586
|
+
params=_list_params(limit=limit, offset=offset),
|
|
1587
|
+
)
|
|
1588
|
+
payload = response.json()
|
|
1589
|
+
raw_items = list(payload.get("data", []))
|
|
1590
|
+
items: list[Template] = [
|
|
1591
|
+
Template.from_dict(data=item) for item in raw_items
|
|
1592
|
+
]
|
|
1593
|
+
return _make_page(items, payload)
|
|
1594
|
+
|
|
1595
|
+
async def get(self, *, template_id: str) -> Template:
|
|
1596
|
+
"""Retrieve an invite template.
|
|
1597
|
+
|
|
1598
|
+
Args:
|
|
1599
|
+
template_id: The id of the template.
|
|
1600
|
+
|
|
1601
|
+
Returns:
|
|
1602
|
+
The template.
|
|
1603
|
+
"""
|
|
1604
|
+
response = await self._request(
|
|
1605
|
+
method="GET",
|
|
1606
|
+
url=f"{_API_V3}/templates/{template_id}",
|
|
1607
|
+
)
|
|
1608
|
+
return Template.from_dict(data=response.json())
|
|
1609
|
+
|
|
1610
|
+
|
|
1611
|
+
@beartype
|
|
1612
|
+
class AsyncUsersNamespace(_AsyncNamespace):
|
|
1613
|
+
"""Async namespace for user operations."""
|
|
1614
|
+
|
|
1615
|
+
async def list(
|
|
1616
|
+
self,
|
|
1617
|
+
*,
|
|
1618
|
+
limit: int | None = None,
|
|
1619
|
+
offset: int | None = None,
|
|
1620
|
+
) -> Page[User]:
|
|
1621
|
+
"""List users.
|
|
1622
|
+
|
|
1623
|
+
Args:
|
|
1624
|
+
limit: Number of records to fetch.
|
|
1625
|
+
offset: Offset of records.
|
|
1626
|
+
|
|
1627
|
+
Returns:
|
|
1628
|
+
A page of users.
|
|
1629
|
+
"""
|
|
1630
|
+
response = await self._request(
|
|
1631
|
+
method="GET",
|
|
1632
|
+
url=f"{_API_V3}/users",
|
|
1633
|
+
params=_list_params(limit=limit, offset=offset),
|
|
1634
|
+
)
|
|
1635
|
+
payload = response.json()
|
|
1636
|
+
raw_items = list(payload.get("data", []))
|
|
1637
|
+
items: list[User] = [User.from_dict(data=item) for item in raw_items]
|
|
1638
|
+
return _make_page(items, payload)
|
|
1639
|
+
|
|
1640
|
+
async def search(
|
|
1641
|
+
self,
|
|
1642
|
+
*,
|
|
1643
|
+
search: str,
|
|
1644
|
+
limit: int | None = None,
|
|
1645
|
+
offset: int | None = None,
|
|
1646
|
+
) -> Page[User]:
|
|
1647
|
+
"""Search users.
|
|
1648
|
+
|
|
1649
|
+
Args:
|
|
1650
|
+
search: Search query.
|
|
1651
|
+
limit: Number of records to fetch.
|
|
1652
|
+
offset: Offset of records.
|
|
1653
|
+
|
|
1654
|
+
Returns:
|
|
1655
|
+
A page of users.
|
|
1656
|
+
"""
|
|
1657
|
+
params = _list_params(
|
|
1658
|
+
limit=limit,
|
|
1659
|
+
offset=offset,
|
|
1660
|
+
extra={"search": search},
|
|
1661
|
+
)
|
|
1662
|
+
response = await self._request(
|
|
1663
|
+
method="GET",
|
|
1664
|
+
url=f"{_API_V3}/users/search",
|
|
1665
|
+
params=params,
|
|
1666
|
+
)
|
|
1667
|
+
payload = response.json()
|
|
1668
|
+
raw_items = list(payload.get("data", []))
|
|
1669
|
+
items: list[User] = [User.from_dict(data=item) for item in raw_items]
|
|
1670
|
+
return _make_page(items, payload)
|
|
1671
|
+
|
|
1672
|
+
async def create(
|
|
1673
|
+
self,
|
|
1674
|
+
*,
|
|
1675
|
+
email: str,
|
|
1676
|
+
firstname: str | None = None,
|
|
1677
|
+
lastname: str | None = None,
|
|
1678
|
+
country: str | None = None,
|
|
1679
|
+
role: str | None = None,
|
|
1680
|
+
send_email: bool | None = None,
|
|
1681
|
+
phone: str | None = None,
|
|
1682
|
+
questions_permission: int | None = None,
|
|
1683
|
+
tests_permission: int | None = None,
|
|
1684
|
+
interviews_permission: int | None = None,
|
|
1685
|
+
candidates_permission: int | None = None,
|
|
1686
|
+
shared_questions_permission: int | None = None,
|
|
1687
|
+
shared_tests_permission: int | None = None,
|
|
1688
|
+
shared_interviews_permission: int | None = None,
|
|
1689
|
+
shared_candidates_permission: int | None = None,
|
|
1690
|
+
company_admin: bool | None = None,
|
|
1691
|
+
team_admin: bool | None = None,
|
|
1692
|
+
teams: Sequence[str] | None = None,
|
|
1693
|
+
) -> User:
|
|
1694
|
+
"""Create a user.
|
|
1695
|
+
|
|
1696
|
+
Args:
|
|
1697
|
+
email: Email address.
|
|
1698
|
+
firstname: First name.
|
|
1699
|
+
lastname: Last name.
|
|
1700
|
+
country: Country.
|
|
1701
|
+
role: Role.
|
|
1702
|
+
send_email: Send invite email flag.
|
|
1703
|
+
phone: Phone number.
|
|
1704
|
+
questions_permission: Permission level.
|
|
1705
|
+
tests_permission: Permission level.
|
|
1706
|
+
interviews_permission: Permission level.
|
|
1707
|
+
candidates_permission: Permission level.
|
|
1708
|
+
shared_questions_permission: Permission level.
|
|
1709
|
+
shared_tests_permission: Permission level.
|
|
1710
|
+
shared_interviews_permission: Permission level.
|
|
1711
|
+
shared_candidates_permission: Permission level.
|
|
1712
|
+
company_admin: Company admin flag.
|
|
1713
|
+
team_admin: Team admin flag.
|
|
1714
|
+
teams: Team ids the user belongs to.
|
|
1715
|
+
|
|
1716
|
+
Returns:
|
|
1717
|
+
The created user.
|
|
1718
|
+
"""
|
|
1719
|
+
body = _drop_none(
|
|
1720
|
+
{
|
|
1721
|
+
"email": email,
|
|
1722
|
+
"firstname": firstname,
|
|
1723
|
+
"lastname": lastname,
|
|
1724
|
+
"country": country,
|
|
1725
|
+
"role": role,
|
|
1726
|
+
"send_email": send_email,
|
|
1727
|
+
"phone": phone,
|
|
1728
|
+
"questions_permission": questions_permission,
|
|
1729
|
+
"tests_permission": tests_permission,
|
|
1730
|
+
"interviews_permission": interviews_permission,
|
|
1731
|
+
"candidates_permission": candidates_permission,
|
|
1732
|
+
"shared_questions_permission": (shared_questions_permission),
|
|
1733
|
+
"shared_tests_permission": (shared_tests_permission),
|
|
1734
|
+
"shared_interviews_permission": (shared_interviews_permission),
|
|
1735
|
+
"shared_candidates_permission": (shared_candidates_permission),
|
|
1736
|
+
"company_admin": company_admin,
|
|
1737
|
+
"team_admin": team_admin,
|
|
1738
|
+
"teams": (
|
|
1739
|
+
[{"id": t} for t in teams] if teams is not None else None
|
|
1740
|
+
),
|
|
1741
|
+
},
|
|
1742
|
+
)
|
|
1743
|
+
response = await self._request(
|
|
1744
|
+
method="POST",
|
|
1745
|
+
url=f"{_API_V3}/users",
|
|
1746
|
+
json=body,
|
|
1747
|
+
)
|
|
1748
|
+
return User.from_dict(data=response.json())
|
|
1749
|
+
|
|
1750
|
+
async def get(self, *, user_id: str) -> User:
|
|
1751
|
+
"""Retrieve a user.
|
|
1752
|
+
|
|
1753
|
+
Args:
|
|
1754
|
+
user_id: The id of the user.
|
|
1755
|
+
|
|
1756
|
+
Returns:
|
|
1757
|
+
The user.
|
|
1758
|
+
"""
|
|
1759
|
+
response = await self._request(
|
|
1760
|
+
method="GET",
|
|
1761
|
+
url=f"{_API_V3}/users/{user_id}",
|
|
1762
|
+
)
|
|
1763
|
+
return User.from_dict(data=response.json())
|
|
1764
|
+
|
|
1765
|
+
async def update(
|
|
1766
|
+
self,
|
|
1767
|
+
*,
|
|
1768
|
+
user_id: str,
|
|
1769
|
+
body: Mapping[str, JSONValue],
|
|
1770
|
+
) -> None:
|
|
1771
|
+
"""Update a user.
|
|
1772
|
+
|
|
1773
|
+
Args:
|
|
1774
|
+
user_id: The id of the user.
|
|
1775
|
+
body: A mapping of fields to update. See the
|
|
1776
|
+
HackerRank API documentation for the
|
|
1777
|
+
supported fields.
|
|
1778
|
+
"""
|
|
1779
|
+
await self._request(
|
|
1780
|
+
method="PUT",
|
|
1781
|
+
url=f"{_API_V3}/users/{user_id}",
|
|
1782
|
+
json=body,
|
|
1783
|
+
)
|
|
1784
|
+
|
|
1785
|
+
async def delete(self, *, user_id: str) -> None:
|
|
1786
|
+
"""Lock (deactivate) a user.
|
|
1787
|
+
|
|
1788
|
+
Args:
|
|
1789
|
+
user_id: The id of the user.
|
|
1790
|
+
"""
|
|
1791
|
+
await self._request(
|
|
1792
|
+
method="DELETE",
|
|
1793
|
+
url=f"{_API_V3}/users/{user_id}",
|
|
1794
|
+
)
|
|
1795
|
+
|
|
1796
|
+
|
|
1797
|
+
@beartype
|
|
1798
|
+
class AsyncTeamMembershipsNamespace(_AsyncNamespace):
|
|
1799
|
+
"""Async namespace for user-team membership operations."""
|
|
1800
|
+
|
|
1801
|
+
async def list(
|
|
1802
|
+
self,
|
|
1803
|
+
*,
|
|
1804
|
+
team_id: str,
|
|
1805
|
+
limit: int | None = None,
|
|
1806
|
+
offset: int | None = None,
|
|
1807
|
+
) -> Page[UserTeamMembership]:
|
|
1808
|
+
"""List memberships for a team.
|
|
1809
|
+
|
|
1810
|
+
Args:
|
|
1811
|
+
team_id: The id of the team.
|
|
1812
|
+
limit: Number of records to fetch.
|
|
1813
|
+
offset: Offset of records.
|
|
1814
|
+
|
|
1815
|
+
Returns:
|
|
1816
|
+
A page of memberships.
|
|
1817
|
+
"""
|
|
1818
|
+
response = await self._request(
|
|
1819
|
+
method="GET",
|
|
1820
|
+
url=f"{_API_V3}/teams/{team_id}/users",
|
|
1821
|
+
params=_list_params(limit=limit, offset=offset),
|
|
1822
|
+
)
|
|
1823
|
+
payload = response.json()
|
|
1824
|
+
raw_items = list(payload.get("data", []))
|
|
1825
|
+
items: list[UserTeamMembership] = [
|
|
1826
|
+
UserTeamMembership.from_dict(data=item) for item in raw_items
|
|
1827
|
+
]
|
|
1828
|
+
return _make_page(items, payload)
|
|
1829
|
+
|
|
1830
|
+
async def get(
|
|
1831
|
+
self,
|
|
1832
|
+
*,
|
|
1833
|
+
team_id: str,
|
|
1834
|
+
user_id: str,
|
|
1835
|
+
) -> UserTeamMembership:
|
|
1836
|
+
"""Retrieve a single membership.
|
|
1837
|
+
|
|
1838
|
+
Args:
|
|
1839
|
+
team_id: The id of the team.
|
|
1840
|
+
user_id: The id of the user.
|
|
1841
|
+
|
|
1842
|
+
Returns:
|
|
1843
|
+
The membership.
|
|
1844
|
+
"""
|
|
1845
|
+
response = await self._request(
|
|
1846
|
+
method="GET",
|
|
1847
|
+
url=f"{_API_V3}/teams/{team_id}/users/{user_id}",
|
|
1848
|
+
)
|
|
1849
|
+
return UserTeamMembership.from_dict(data=response.json())
|
|
1850
|
+
|
|
1851
|
+
async def create(
|
|
1852
|
+
self,
|
|
1853
|
+
*,
|
|
1854
|
+
team_id: str,
|
|
1855
|
+
user_id: str,
|
|
1856
|
+
license: str | None = None, # noqa: A002 # pylint: disable=redefined-builtin
|
|
1857
|
+
) -> UserTeamMembership:
|
|
1858
|
+
"""Add a user to a team.
|
|
1859
|
+
|
|
1860
|
+
Args:
|
|
1861
|
+
team_id: The id of the team.
|
|
1862
|
+
user_id: The id of the user.
|
|
1863
|
+
license: License kind to assign.
|
|
1864
|
+
|
|
1865
|
+
Returns:
|
|
1866
|
+
The created membership.
|
|
1867
|
+
"""
|
|
1868
|
+
params: dict[str, str | int] = {}
|
|
1869
|
+
if license is not None:
|
|
1870
|
+
params["license"] = license
|
|
1871
|
+
response = await self._request(
|
|
1872
|
+
method="POST",
|
|
1873
|
+
url=f"{_API_V3}/teams/{team_id}/users/{user_id}",
|
|
1874
|
+
params=params or None,
|
|
1875
|
+
)
|
|
1876
|
+
return UserTeamMembership.from_dict(data=response.json())
|
|
1877
|
+
|
|
1878
|
+
async def delete(
|
|
1879
|
+
self,
|
|
1880
|
+
*,
|
|
1881
|
+
team_id: str,
|
|
1882
|
+
user_id: str,
|
|
1883
|
+
) -> None:
|
|
1884
|
+
"""Remove a user from a team.
|
|
1885
|
+
|
|
1886
|
+
Args:
|
|
1887
|
+
team_id: The id of the team.
|
|
1888
|
+
user_id: The id of the user.
|
|
1889
|
+
"""
|
|
1890
|
+
await self._request(
|
|
1891
|
+
method="DELETE",
|
|
1892
|
+
url=f"{_API_V3}/teams/{team_id}/users/{user_id}",
|
|
1893
|
+
)
|
|
1894
|
+
|
|
1895
|
+
|
|
1896
|
+
@beartype
|
|
1897
|
+
class AsyncTeamsNamespace(_AsyncNamespace):
|
|
1898
|
+
"""Async namespace for team operations."""
|
|
1899
|
+
|
|
1900
|
+
def __init__(
|
|
1901
|
+
self,
|
|
1902
|
+
*,
|
|
1903
|
+
transport: AsyncTransport,
|
|
1904
|
+
base_url: str,
|
|
1905
|
+
headers: dict[str, str],
|
|
1906
|
+
) -> None:
|
|
1907
|
+
"""Create the namespace.
|
|
1908
|
+
|
|
1909
|
+
Args:
|
|
1910
|
+
transport: The HTTP transport.
|
|
1911
|
+
base_url: The base URL for the API.
|
|
1912
|
+
headers: Headers to send with every request.
|
|
1913
|
+
"""
|
|
1914
|
+
super().__init__(
|
|
1915
|
+
transport=transport,
|
|
1916
|
+
base_url=base_url,
|
|
1917
|
+
headers=headers,
|
|
1918
|
+
)
|
|
1919
|
+
self.memberships: AsyncTeamMembershipsNamespace = (
|
|
1920
|
+
AsyncTeamMembershipsNamespace(
|
|
1921
|
+
transport=transport,
|
|
1922
|
+
base_url=base_url,
|
|
1923
|
+
headers=headers,
|
|
1924
|
+
)
|
|
1925
|
+
)
|
|
1926
|
+
|
|
1927
|
+
async def list(
|
|
1928
|
+
self,
|
|
1929
|
+
*,
|
|
1930
|
+
limit: int | None = None,
|
|
1931
|
+
offset: int | None = None,
|
|
1932
|
+
) -> Page[Team]:
|
|
1933
|
+
"""List teams.
|
|
1934
|
+
|
|
1935
|
+
Args:
|
|
1936
|
+
limit: Number of records to fetch.
|
|
1937
|
+
offset: Offset of records.
|
|
1938
|
+
|
|
1939
|
+
Returns:
|
|
1940
|
+
A page of teams.
|
|
1941
|
+
"""
|
|
1942
|
+
response = await self._request(
|
|
1943
|
+
method="GET",
|
|
1944
|
+
url=f"{_API_V3}/teams",
|
|
1945
|
+
params=_list_params(limit=limit, offset=offset),
|
|
1946
|
+
)
|
|
1947
|
+
payload = response.json()
|
|
1948
|
+
raw_items = list(payload.get("data", []))
|
|
1949
|
+
items: list[Team] = [Team.from_dict(data=item) for item in raw_items]
|
|
1950
|
+
return _make_page(items, payload)
|
|
1951
|
+
|
|
1952
|
+
async def create(
|
|
1953
|
+
self,
|
|
1954
|
+
*,
|
|
1955
|
+
name: str,
|
|
1956
|
+
recruiter_cap: int | None = None,
|
|
1957
|
+
developer_cap: int | None = None,
|
|
1958
|
+
invite_as: str | None = None,
|
|
1959
|
+
locations: Sequence[str] | None = None,
|
|
1960
|
+
departments: Sequence[str] | None = None,
|
|
1961
|
+
) -> Team:
|
|
1962
|
+
"""Create a team.
|
|
1963
|
+
|
|
1964
|
+
Args:
|
|
1965
|
+
name: Team name.
|
|
1966
|
+
recruiter_cap: Recruiter seat cap.
|
|
1967
|
+
developer_cap: Developer seat cap.
|
|
1968
|
+
invite_as: Default role for invites.
|
|
1969
|
+
locations: Allowed locations.
|
|
1970
|
+
departments: Allowed departments.
|
|
1971
|
+
|
|
1972
|
+
Returns:
|
|
1973
|
+
The created team.
|
|
1974
|
+
"""
|
|
1975
|
+
body = _drop_none(
|
|
1976
|
+
{
|
|
1977
|
+
"name": name,
|
|
1978
|
+
"recruiter_cap": recruiter_cap,
|
|
1979
|
+
"developer_cap": developer_cap,
|
|
1980
|
+
"invite_as": invite_as,
|
|
1981
|
+
"locations": (
|
|
1982
|
+
list(locations) if locations is not None else None
|
|
1983
|
+
),
|
|
1984
|
+
"departments": (
|
|
1985
|
+
list(departments) if departments is not None else None
|
|
1986
|
+
),
|
|
1987
|
+
},
|
|
1988
|
+
)
|
|
1989
|
+
response = await self._request(
|
|
1990
|
+
method="POST",
|
|
1991
|
+
url=f"{_API_V3}/teams",
|
|
1992
|
+
json=body,
|
|
1993
|
+
)
|
|
1994
|
+
return Team.from_dict(data=response.json())
|
|
1995
|
+
|
|
1996
|
+
async def get(self, *, team_id: str) -> Team:
|
|
1997
|
+
"""Retrieve a team.
|
|
1998
|
+
|
|
1999
|
+
Args:
|
|
2000
|
+
team_id: The id of the team.
|
|
2001
|
+
|
|
2002
|
+
Returns:
|
|
2003
|
+
The team.
|
|
2004
|
+
"""
|
|
2005
|
+
response = await self._request(
|
|
2006
|
+
method="GET",
|
|
2007
|
+
url=f"{_API_V3}/teams/{team_id}",
|
|
2008
|
+
)
|
|
2009
|
+
return Team.from_dict(data=response.json())
|
|
2010
|
+
|
|
2011
|
+
async def update(
|
|
2012
|
+
self,
|
|
2013
|
+
*,
|
|
2014
|
+
team_id: str,
|
|
2015
|
+
name: str | None = None,
|
|
2016
|
+
recruiter_cap: int | None = None,
|
|
2017
|
+
developer_cap: int | None = None,
|
|
2018
|
+
invite_as: str | None = None,
|
|
2019
|
+
locations: Sequence[str] | None = None,
|
|
2020
|
+
departments: Sequence[str] | None = None,
|
|
2021
|
+
) -> None:
|
|
2022
|
+
"""Update a team.
|
|
2023
|
+
|
|
2024
|
+
Args:
|
|
2025
|
+
team_id: The id of the team.
|
|
2026
|
+
name: New team name.
|
|
2027
|
+
recruiter_cap: New recruiter seat cap.
|
|
2028
|
+
developer_cap: New developer seat cap.
|
|
2029
|
+
invite_as: New default invite role.
|
|
2030
|
+
locations: New allowed locations.
|
|
2031
|
+
departments: New allowed departments.
|
|
2032
|
+
"""
|
|
2033
|
+
body = _drop_none(
|
|
2034
|
+
{
|
|
2035
|
+
"name": name,
|
|
2036
|
+
"recruiter_cap": recruiter_cap,
|
|
2037
|
+
"developer_cap": developer_cap,
|
|
2038
|
+
"invite_as": invite_as,
|
|
2039
|
+
"locations": (
|
|
2040
|
+
list(locations) if locations is not None else None
|
|
2041
|
+
),
|
|
2042
|
+
"departments": (
|
|
2043
|
+
list(departments) if departments is not None else None
|
|
2044
|
+
),
|
|
2045
|
+
},
|
|
2046
|
+
)
|
|
2047
|
+
await self._request(
|
|
2048
|
+
method="PUT",
|
|
2049
|
+
url=f"{_API_V3}/teams/{team_id}",
|
|
2050
|
+
json=body,
|
|
2051
|
+
)
|
|
2052
|
+
|
|
2053
|
+
async def delete(self, *, team_id: str) -> None:
|
|
2054
|
+
"""Delete a team.
|
|
2055
|
+
|
|
2056
|
+
Args:
|
|
2057
|
+
team_id: The id of the team.
|
|
2058
|
+
"""
|
|
2059
|
+
await self._request(
|
|
2060
|
+
method="DELETE",
|
|
2061
|
+
url=f"{_API_V3}/teams/{team_id}",
|
|
2062
|
+
)
|
|
2063
|
+
|
|
2064
|
+
|
|
2065
|
+
@beartype
|
|
2066
|
+
class AsyncAuditLogsNamespace(_AsyncNamespace):
|
|
2067
|
+
"""Async namespace for audit-log operations."""
|
|
2068
|
+
|
|
2069
|
+
async def list(
|
|
2070
|
+
self,
|
|
2071
|
+
*,
|
|
2072
|
+
limit: int | None = None,
|
|
2073
|
+
offset: int | None = None,
|
|
2074
|
+
user_id: str | None = None,
|
|
2075
|
+
) -> Page[AuditLog]:
|
|
2076
|
+
"""List audit logs.
|
|
2077
|
+
|
|
2078
|
+
Args:
|
|
2079
|
+
limit: Number of records to fetch.
|
|
2080
|
+
offset: Offset of records.
|
|
2081
|
+
user_id: Filter to a specific user.
|
|
2082
|
+
|
|
2083
|
+
Returns:
|
|
2084
|
+
A page of audit logs.
|
|
2085
|
+
"""
|
|
2086
|
+
extra: dict[str, str | int] = {}
|
|
2087
|
+
if user_id is not None:
|
|
2088
|
+
extra["user_id"] = user_id
|
|
2089
|
+
response = await self._request(
|
|
2090
|
+
method="GET",
|
|
2091
|
+
url=f"{_API_V3}/audit_log",
|
|
2092
|
+
params=_list_params(
|
|
2093
|
+
limit=limit,
|
|
2094
|
+
offset=offset,
|
|
2095
|
+
extra=extra,
|
|
2096
|
+
),
|
|
2097
|
+
)
|
|
2098
|
+
payload = response.json()
|
|
2099
|
+
raw_items = list(payload.get("data", []))
|
|
2100
|
+
items: list[AuditLog] = [
|
|
2101
|
+
AuditLog.from_dict(data=item) for item in raw_items
|
|
2102
|
+
]
|
|
2103
|
+
return _make_page(items, payload)
|
|
2104
|
+
|
|
2105
|
+
|
|
2106
|
+
@beartype
|
|
2107
|
+
class AsyncATSCodePairNamespace(_AsyncNamespace):
|
|
2108
|
+
"""Async namespace for ATS Codepair operations."""
|
|
2109
|
+
|
|
2110
|
+
async def invite(
|
|
2111
|
+
self,
|
|
2112
|
+
*,
|
|
2113
|
+
title: str | None = None,
|
|
2114
|
+
requisition_id: str | None = None,
|
|
2115
|
+
candidate_id: str | None = None,
|
|
2116
|
+
candidate: Mapping[str, JSONValue] | None = None,
|
|
2117
|
+
send_email: bool | None = None,
|
|
2118
|
+
interview_metadata: Mapping[str, JSONValue] | None = None,
|
|
2119
|
+
) -> ATSCodePair:
|
|
2120
|
+
"""Invite a candidate to an ATS Codepair interview.
|
|
2121
|
+
|
|
2122
|
+
Args:
|
|
2123
|
+
title: The interview title.
|
|
2124
|
+
requisition_id: The ATS requisition id.
|
|
2125
|
+
candidate_id: The ATS candidate id.
|
|
2126
|
+
candidate: Candidate information.
|
|
2127
|
+
send_email: Whether to send an email.
|
|
2128
|
+
interview_metadata: Arbitrary metadata.
|
|
2129
|
+
|
|
2130
|
+
Returns:
|
|
2131
|
+
The ATS Codepair result.
|
|
2132
|
+
"""
|
|
2133
|
+
body = _drop_none(
|
|
2134
|
+
{
|
|
2135
|
+
"title": title,
|
|
2136
|
+
"requisition_id": requisition_id,
|
|
2137
|
+
"candidate_id": candidate_id,
|
|
2138
|
+
"candidate": candidate,
|
|
2139
|
+
"send_email": send_email,
|
|
2140
|
+
"interview_metadata": interview_metadata,
|
|
2141
|
+
},
|
|
2142
|
+
)
|
|
2143
|
+
response = await self._request(
|
|
2144
|
+
method="POST",
|
|
2145
|
+
url=f"{_API_V3}/ats/codepair",
|
|
2146
|
+
json=body,
|
|
2147
|
+
)
|
|
2148
|
+
return ATSCodePair.from_dict(data=response.json())
|
|
2149
|
+
|
|
2150
|
+
|
|
2151
|
+
@beartype
|
|
2152
|
+
class AsyncATSCodeScreenNamespace(_AsyncNamespace):
|
|
2153
|
+
"""Async namespace for ATS CodeScreen operations."""
|
|
2154
|
+
|
|
2155
|
+
async def invite(
|
|
2156
|
+
self,
|
|
2157
|
+
*,
|
|
2158
|
+
test_id: str,
|
|
2159
|
+
email: str,
|
|
2160
|
+
requisition_id: str | None = None,
|
|
2161
|
+
candidate_id: str | None = None,
|
|
2162
|
+
send_email: bool | None = None,
|
|
2163
|
+
test_result_url: str | None = None,
|
|
2164
|
+
webhook_authentication: Mapping[str, JSONValue] | None = None,
|
|
2165
|
+
accept_result_updates: bool | None = None,
|
|
2166
|
+
force: bool | None = None,
|
|
2167
|
+
force_reattempt_after: int | None = None,
|
|
2168
|
+
accommodations: Mapping[str, JSONValue] | None = None,
|
|
2169
|
+
) -> ATSCodeScreen:
|
|
2170
|
+
"""Invite a candidate to a CodeScreen test.
|
|
2171
|
+
|
|
2172
|
+
Args:
|
|
2173
|
+
test_id: The test id.
|
|
2174
|
+
email: Candidate email.
|
|
2175
|
+
requisition_id: ATS requisition id.
|
|
2176
|
+
candidate_id: ATS candidate id.
|
|
2177
|
+
send_email: Whether to send an email.
|
|
2178
|
+
test_result_url: URL for posting results.
|
|
2179
|
+
webhook_authentication: Webhook auth config.
|
|
2180
|
+
accept_result_updates: Accept-updates flag.
|
|
2181
|
+
force: Force inviting flag.
|
|
2182
|
+
force_reattempt_after: Seconds before re-attempt.
|
|
2183
|
+
accommodations: Accommodation settings.
|
|
2184
|
+
|
|
2185
|
+
Returns:
|
|
2186
|
+
The ATS CodeScreen result.
|
|
2187
|
+
"""
|
|
2188
|
+
body = _drop_none(
|
|
2189
|
+
{
|
|
2190
|
+
"test_id": test_id,
|
|
2191
|
+
"email": email,
|
|
2192
|
+
"requisition_id": requisition_id,
|
|
2193
|
+
"candidate_id": candidate_id,
|
|
2194
|
+
"send_email": send_email,
|
|
2195
|
+
"test_result_url": test_result_url,
|
|
2196
|
+
"webhook_authentication": webhook_authentication,
|
|
2197
|
+
"accept_result_updates": accept_result_updates,
|
|
2198
|
+
"force": force,
|
|
2199
|
+
"force_reattempt_after": force_reattempt_after,
|
|
2200
|
+
"accommodations": accommodations,
|
|
2201
|
+
},
|
|
2202
|
+
)
|
|
2203
|
+
response = await self._request(
|
|
2204
|
+
method="POST",
|
|
2205
|
+
url=f"{_API_V3}/ats/codescreen",
|
|
2206
|
+
json=body,
|
|
2207
|
+
)
|
|
2208
|
+
return ATSCodeScreen.from_dict(data=response.json())
|
|
2209
|
+
|
|
2210
|
+
|
|
2211
|
+
@beartype
|
|
2212
|
+
class AsyncATSNamespace(_AsyncNamespace):
|
|
2213
|
+
"""Async namespace for ATS operations."""
|
|
2214
|
+
|
|
2215
|
+
def __init__(
|
|
2216
|
+
self,
|
|
2217
|
+
*,
|
|
2218
|
+
transport: AsyncTransport,
|
|
2219
|
+
base_url: str,
|
|
2220
|
+
headers: dict[str, str],
|
|
2221
|
+
) -> None:
|
|
2222
|
+
"""Create the ATS namespace.
|
|
2223
|
+
|
|
2224
|
+
Args:
|
|
2225
|
+
transport: The HTTP transport.
|
|
2226
|
+
base_url: The base URL for the API.
|
|
2227
|
+
headers: Headers to send with every request.
|
|
2228
|
+
"""
|
|
2229
|
+
super().__init__(
|
|
2230
|
+
transport=transport,
|
|
2231
|
+
base_url=base_url,
|
|
2232
|
+
headers=headers,
|
|
2233
|
+
)
|
|
2234
|
+
self.codepair: AsyncATSCodePairNamespace = AsyncATSCodePairNamespace(
|
|
2235
|
+
transport=transport,
|
|
2236
|
+
base_url=base_url,
|
|
2237
|
+
headers=headers,
|
|
2238
|
+
)
|
|
2239
|
+
self.codescreen: AsyncATSCodeScreenNamespace = (
|
|
2240
|
+
AsyncATSCodeScreenNamespace(
|
|
2241
|
+
transport=transport,
|
|
2242
|
+
base_url=base_url,
|
|
2243
|
+
headers=headers,
|
|
2244
|
+
)
|
|
2245
|
+
)
|
|
2246
|
+
|
|
2247
|
+
|
|
2248
|
+
@beartype
|
|
2249
|
+
class AsyncSCIMUsersNamespace(_AsyncNamespace):
|
|
2250
|
+
"""Async namespace for SCIM v2 user operations."""
|
|
2251
|
+
|
|
2252
|
+
async def list(
|
|
2253
|
+
self,
|
|
2254
|
+
*,
|
|
2255
|
+
limit: int | None = None,
|
|
2256
|
+
offset: int | None = None,
|
|
2257
|
+
) -> SCIMPage[SCIMUser]:
|
|
2258
|
+
"""List SCIM users.
|
|
2259
|
+
|
|
2260
|
+
Args:
|
|
2261
|
+
limit: Number of records to fetch.
|
|
2262
|
+
offset: Offset of records.
|
|
2263
|
+
|
|
2264
|
+
Returns:
|
|
2265
|
+
A page of SCIM users.
|
|
2266
|
+
"""
|
|
2267
|
+
response = await self._request(
|
|
2268
|
+
method="GET",
|
|
2269
|
+
url="/Users",
|
|
2270
|
+
params=_list_params(limit=limit, offset=offset),
|
|
2271
|
+
)
|
|
2272
|
+
payload = response.json()
|
|
2273
|
+
raw = list(payload.get("Resources", []))
|
|
2274
|
+
items: list[SCIMUser] = [SCIMUser.from_dict(data=item) for item in raw]
|
|
2275
|
+
return _make_scim_page(items, payload)
|
|
2276
|
+
|
|
2277
|
+
async def create(
|
|
2278
|
+
self,
|
|
2279
|
+
*,
|
|
2280
|
+
body: Mapping[str, JSONValue],
|
|
2281
|
+
) -> SCIMUser:
|
|
2282
|
+
"""Create a SCIM user.
|
|
2283
|
+
|
|
2284
|
+
Args:
|
|
2285
|
+
body: The SCIM user payload.
|
|
2286
|
+
|
|
2287
|
+
Returns:
|
|
2288
|
+
The created SCIM user.
|
|
2289
|
+
"""
|
|
2290
|
+
response = await self._request(
|
|
2291
|
+
method="POST",
|
|
2292
|
+
url="/Users",
|
|
2293
|
+
json=body,
|
|
2294
|
+
)
|
|
2295
|
+
return SCIMUser.from_dict(data=response.json())
|
|
2296
|
+
|
|
2297
|
+
async def get(self, *, scim_user_id: str) -> SCIMUser:
|
|
2298
|
+
"""Retrieve a SCIM user.
|
|
2299
|
+
|
|
2300
|
+
Args:
|
|
2301
|
+
scim_user_id: The id of the SCIM user.
|
|
2302
|
+
|
|
2303
|
+
Returns:
|
|
2304
|
+
The SCIM user.
|
|
2305
|
+
"""
|
|
2306
|
+
response = await self._request(
|
|
2307
|
+
method="GET",
|
|
2308
|
+
url=f"/Users/{scim_user_id}",
|
|
2309
|
+
)
|
|
2310
|
+
return SCIMUser.from_dict(data=response.json())
|
|
2311
|
+
|
|
2312
|
+
async def replace(
|
|
2313
|
+
self,
|
|
2314
|
+
*,
|
|
2315
|
+
scim_user_id: str,
|
|
2316
|
+
body: Mapping[str, JSONValue],
|
|
2317
|
+
) -> SCIMUser:
|
|
2318
|
+
"""Replace a SCIM user (PUT).
|
|
2319
|
+
|
|
2320
|
+
Args:
|
|
2321
|
+
scim_user_id: The id of the SCIM user.
|
|
2322
|
+
body: The full SCIM user payload.
|
|
2323
|
+
|
|
2324
|
+
Returns:
|
|
2325
|
+
The updated SCIM user.
|
|
2326
|
+
"""
|
|
2327
|
+
response = await self._request(
|
|
2328
|
+
method="PUT",
|
|
2329
|
+
url=f"/Users/{scim_user_id}",
|
|
2330
|
+
json=body,
|
|
2331
|
+
)
|
|
2332
|
+
return SCIMUser.from_dict(data=response.json())
|
|
2333
|
+
|
|
2334
|
+
async def patch(
|
|
2335
|
+
self,
|
|
2336
|
+
*,
|
|
2337
|
+
scim_user_id: str,
|
|
2338
|
+
operations: Sequence[Mapping[str, JSONValue]],
|
|
2339
|
+
) -> SCIMUser:
|
|
2340
|
+
"""Patch a SCIM user.
|
|
2341
|
+
|
|
2342
|
+
Args:
|
|
2343
|
+
scim_user_id: The id of the SCIM user.
|
|
2344
|
+
operations: The SCIM patch operations.
|
|
2345
|
+
|
|
2346
|
+
Returns:
|
|
2347
|
+
The updated SCIM user.
|
|
2348
|
+
"""
|
|
2349
|
+
body: dict[str, JSONValue] = {
|
|
2350
|
+
"operations": [dict(op) for op in operations],
|
|
2351
|
+
}
|
|
2352
|
+
response = await self._request(
|
|
2353
|
+
method="PATCH",
|
|
2354
|
+
url=f"/Users/{scim_user_id}",
|
|
2355
|
+
json=body,
|
|
2356
|
+
)
|
|
2357
|
+
return SCIMUser.from_dict(data=response.json())
|
|
2358
|
+
|
|
2359
|
+
async def delete(self, *, scim_user_id: str) -> None:
|
|
2360
|
+
"""Lock a SCIM user.
|
|
2361
|
+
|
|
2362
|
+
Args:
|
|
2363
|
+
scim_user_id: The id of the SCIM user.
|
|
2364
|
+
"""
|
|
2365
|
+
await self._request(
|
|
2366
|
+
method="DELETE",
|
|
2367
|
+
url=f"/Users/{scim_user_id}",
|
|
2368
|
+
)
|
|
2369
|
+
|
|
2370
|
+
|
|
2371
|
+
@beartype
|
|
2372
|
+
class AsyncSCIMGroupsNamespace(_AsyncNamespace):
|
|
2373
|
+
"""Async namespace for SCIM v2 group (team) operations."""
|
|
2374
|
+
|
|
2375
|
+
async def list(
|
|
2376
|
+
self,
|
|
2377
|
+
*,
|
|
2378
|
+
limit: int | None = None,
|
|
2379
|
+
offset: int | None = None,
|
|
2380
|
+
) -> SCIMPage[SCIMTeam]:
|
|
2381
|
+
"""List SCIM groups.
|
|
2382
|
+
|
|
2383
|
+
Args:
|
|
2384
|
+
limit: Number of records to fetch.
|
|
2385
|
+
offset: Offset of records.
|
|
2386
|
+
|
|
2387
|
+
Returns:
|
|
2388
|
+
A page of SCIM teams.
|
|
2389
|
+
"""
|
|
2390
|
+
response = await self._request(
|
|
2391
|
+
method="GET",
|
|
2392
|
+
url="/Groups",
|
|
2393
|
+
params=_list_params(limit=limit, offset=offset),
|
|
2394
|
+
)
|
|
2395
|
+
payload = response.json()
|
|
2396
|
+
raw = list(payload.get("Resources", []))
|
|
2397
|
+
items: list[SCIMTeam] = [SCIMTeam.from_dict(data=item) for item in raw]
|
|
2398
|
+
return _make_scim_page(items, payload)
|
|
2399
|
+
|
|
2400
|
+
async def create(self, *, body: Mapping[str, JSONValue]) -> SCIMTeam:
|
|
2401
|
+
"""Create a SCIM group.
|
|
2402
|
+
|
|
2403
|
+
Args:
|
|
2404
|
+
body: The SCIM group payload.
|
|
2405
|
+
|
|
2406
|
+
Returns:
|
|
2407
|
+
The created SCIM team.
|
|
2408
|
+
"""
|
|
2409
|
+
response = await self._request(
|
|
2410
|
+
method="POST",
|
|
2411
|
+
url="/Groups",
|
|
2412
|
+
json=body,
|
|
2413
|
+
)
|
|
2414
|
+
return SCIMTeam.from_dict(data=response.json())
|
|
2415
|
+
|
|
2416
|
+
async def get(self, *, scim_group_id: str) -> SCIMTeam:
|
|
2417
|
+
"""Retrieve a SCIM group.
|
|
2418
|
+
|
|
2419
|
+
Args:
|
|
2420
|
+
scim_group_id: The id of the SCIM group.
|
|
2421
|
+
|
|
2422
|
+
Returns:
|
|
2423
|
+
The SCIM team.
|
|
2424
|
+
"""
|
|
2425
|
+
response = await self._request(
|
|
2426
|
+
method="GET",
|
|
2427
|
+
url=f"/Groups/{scim_group_id}",
|
|
2428
|
+
)
|
|
2429
|
+
return SCIMTeam.from_dict(data=response.json())
|
|
2430
|
+
|
|
2431
|
+
async def patch(
|
|
2432
|
+
self,
|
|
2433
|
+
*,
|
|
2434
|
+
scim_group_id: str,
|
|
2435
|
+
operations: Sequence[Mapping[str, JSONValue]],
|
|
2436
|
+
) -> SCIMTeam:
|
|
2437
|
+
"""Patch a SCIM group.
|
|
2438
|
+
|
|
2439
|
+
Args:
|
|
2440
|
+
scim_group_id: The id of the SCIM group.
|
|
2441
|
+
operations: The SCIM patch operations.
|
|
2442
|
+
|
|
2443
|
+
Returns:
|
|
2444
|
+
The updated SCIM team.
|
|
2445
|
+
"""
|
|
2446
|
+
body: dict[str, JSONValue] = {
|
|
2447
|
+
"operations": [dict(op) for op in operations],
|
|
2448
|
+
}
|
|
2449
|
+
response = await self._request(
|
|
2450
|
+
method="PATCH",
|
|
2451
|
+
url=f"/Groups/{scim_group_id}",
|
|
2452
|
+
json=body,
|
|
2453
|
+
)
|
|
2454
|
+
return SCIMTeam.from_dict(data=response.json())
|
|
2455
|
+
|
|
2456
|
+
async def delete(self, *, scim_group_id: str) -> None:
|
|
2457
|
+
"""Deprovision a SCIM group.
|
|
2458
|
+
|
|
2459
|
+
Args:
|
|
2460
|
+
scim_group_id: The id of the SCIM group.
|
|
2461
|
+
"""
|
|
2462
|
+
await self._request(
|
|
2463
|
+
method="DELETE",
|
|
2464
|
+
url=f"/Groups/{scim_group_id}",
|
|
2465
|
+
)
|
|
2466
|
+
|
|
2467
|
+
|
|
2468
|
+
@beartype
|
|
2469
|
+
class AsyncSCIMNamespace(_AsyncNamespace):
|
|
2470
|
+
"""Async namespace for SCIM v2 operations."""
|
|
2471
|
+
|
|
2472
|
+
def __init__(
|
|
2473
|
+
self,
|
|
2474
|
+
*,
|
|
2475
|
+
transport: AsyncTransport,
|
|
2476
|
+
base_url: str,
|
|
2477
|
+
headers: dict[str, str],
|
|
2478
|
+
) -> None:
|
|
2479
|
+
"""Create the SCIM namespace.
|
|
2480
|
+
|
|
2481
|
+
Args:
|
|
2482
|
+
transport: The HTTP transport.
|
|
2483
|
+
base_url: The base URL for the API.
|
|
2484
|
+
headers: Headers to send with every request.
|
|
2485
|
+
"""
|
|
2486
|
+
super().__init__(
|
|
2487
|
+
transport=transport,
|
|
2488
|
+
base_url=base_url,
|
|
2489
|
+
headers=headers,
|
|
2490
|
+
)
|
|
2491
|
+
self.users: AsyncSCIMUsersNamespace = AsyncSCIMUsersNamespace(
|
|
2492
|
+
transport=transport,
|
|
2493
|
+
base_url=base_url,
|
|
2494
|
+
headers=headers,
|
|
2495
|
+
)
|
|
2496
|
+
self.groups: AsyncSCIMGroupsNamespace = AsyncSCIMGroupsNamespace(
|
|
2497
|
+
transport=transport,
|
|
2498
|
+
base_url=base_url,
|
|
2499
|
+
headers=headers,
|
|
2500
|
+
)
|
|
2501
|
+
|
|
2502
|
+
|
|
2503
|
+
@beartype
|
|
2504
|
+
class AsyncHackerRank:
|
|
2505
|
+
"""An async client for the HackerRank for Work API."""
|
|
2506
|
+
|
|
2507
|
+
def __init__(
|
|
2508
|
+
self,
|
|
2509
|
+
*,
|
|
2510
|
+
api_key: str,
|
|
2511
|
+
base_url: str = "https://www.hackerrank.com",
|
|
2512
|
+
scim_base_url: str = _SCIM_BASE_URL,
|
|
2513
|
+
transport: AsyncTransport | None = None,
|
|
2514
|
+
) -> None:
|
|
2515
|
+
"""Create a new async HackerRank client.
|
|
2516
|
+
|
|
2517
|
+
Args:
|
|
2518
|
+
api_key: The API key for authentication.
|
|
2519
|
+
base_url: The base URL for the v3 API.
|
|
2520
|
+
scim_base_url: The base URL for the SCIM v2 API. This
|
|
2521
|
+
is a different host from ``base_url``; the two APIs
|
|
2522
|
+
cannot share a base URL.
|
|
2523
|
+
transport: The HTTP transport. Defaults to
|
|
2524
|
+
``AsyncHTTPXTransport()``.
|
|
2525
|
+
"""
|
|
2526
|
+
self.base_url = base_url
|
|
2527
|
+
self.scim_base_url = scim_base_url
|
|
2528
|
+
resolved_transport = transport or AsyncHTTPXTransport()
|
|
2529
|
+
headers = {
|
|
2530
|
+
"Authorization": f"Bearer {api_key}",
|
|
2531
|
+
"Accept": "application/json",
|
|
2532
|
+
}
|
|
2533
|
+
self.interviews: AsyncInterviewsNamespace = AsyncInterviewsNamespace(
|
|
2534
|
+
transport=resolved_transport,
|
|
2535
|
+
base_url=base_url,
|
|
2536
|
+
headers=headers,
|
|
2537
|
+
)
|
|
2538
|
+
self.interview_templates: AsyncInterviewTemplatesNamespace = (
|
|
2539
|
+
AsyncInterviewTemplatesNamespace(
|
|
2540
|
+
transport=resolved_transport,
|
|
2541
|
+
base_url=base_url,
|
|
2542
|
+
headers=headers,
|
|
2543
|
+
)
|
|
2544
|
+
)
|
|
2545
|
+
self.environments: AsyncEnvironmentsNamespace = (
|
|
2546
|
+
AsyncEnvironmentsNamespace(
|
|
2547
|
+
transport=resolved_transport,
|
|
2548
|
+
base_url=base_url,
|
|
2549
|
+
headers=headers,
|
|
2550
|
+
)
|
|
2551
|
+
)
|
|
2552
|
+
self.questions: AsyncQuestionsNamespace = AsyncQuestionsNamespace(
|
|
2553
|
+
transport=resolved_transport,
|
|
2554
|
+
base_url=base_url,
|
|
2555
|
+
headers=headers,
|
|
2556
|
+
)
|
|
2557
|
+
self.tests: AsyncTestsNamespace = AsyncTestsNamespace(
|
|
2558
|
+
transport=resolved_transport,
|
|
2559
|
+
base_url=base_url,
|
|
2560
|
+
headers=headers,
|
|
2561
|
+
)
|
|
2562
|
+
self.templates: AsyncTemplatesNamespace = AsyncTemplatesNamespace(
|
|
2563
|
+
transport=resolved_transport,
|
|
2564
|
+
base_url=base_url,
|
|
2565
|
+
headers=headers,
|
|
2566
|
+
)
|
|
2567
|
+
self.users: AsyncUsersNamespace = AsyncUsersNamespace(
|
|
2568
|
+
transport=resolved_transport,
|
|
2569
|
+
base_url=base_url,
|
|
2570
|
+
headers=headers,
|
|
2571
|
+
)
|
|
2572
|
+
self.teams: AsyncTeamsNamespace = AsyncTeamsNamespace(
|
|
2573
|
+
transport=resolved_transport,
|
|
2574
|
+
base_url=base_url,
|
|
2575
|
+
headers=headers,
|
|
2576
|
+
)
|
|
2577
|
+
self.audit_logs: AsyncAuditLogsNamespace = AsyncAuditLogsNamespace(
|
|
2578
|
+
transport=resolved_transport,
|
|
2579
|
+
base_url=base_url,
|
|
2580
|
+
headers=headers,
|
|
2581
|
+
)
|
|
2582
|
+
self.ats: AsyncATSNamespace = AsyncATSNamespace(
|
|
2583
|
+
transport=resolved_transport,
|
|
2584
|
+
base_url=base_url,
|
|
2585
|
+
headers=headers,
|
|
2586
|
+
)
|
|
2587
|
+
self.scim: AsyncSCIMNamespace = AsyncSCIMNamespace(
|
|
2588
|
+
transport=resolved_transport,
|
|
2589
|
+
base_url=scim_base_url,
|
|
2590
|
+
headers=headers,
|
|
2591
|
+
)
|
|
2592
|
+
self._owned_transport = (
|
|
2593
|
+
resolved_transport
|
|
2594
|
+
if isinstance(resolved_transport, AsyncHTTPXTransport)
|
|
2595
|
+
else None
|
|
2596
|
+
)
|
|
2597
|
+
|
|
2598
|
+
async def aclose(self) -> None:
|
|
2599
|
+
"""Close the underlying transport if it supports closing."""
|
|
2600
|
+
if self._owned_transport is not None:
|
|
2601
|
+
await self._owned_transport.aclose()
|
|
2602
|
+
|
|
2603
|
+
async def __aenter__(self) -> Self:
|
|
2604
|
+
"""Enter the async context manager.
|
|
2605
|
+
|
|
2606
|
+
Returns:
|
|
2607
|
+
This client instance.
|
|
2608
|
+
"""
|
|
2609
|
+
return self
|
|
2610
|
+
|
|
2611
|
+
async def __aexit__(
|
|
2612
|
+
self,
|
|
2613
|
+
_exc_type: type[BaseException] | None,
|
|
2614
|
+
_exc_val: BaseException | None,
|
|
2615
|
+
_exc_tb: TracebackType | None,
|
|
2616
|
+
/,
|
|
2617
|
+
) -> None:
|
|
2618
|
+
"""Exit the async context manager and close."""
|
|
2619
|
+
await self.aclose()
|