hackerrank 2026.7.30__tar.gz → 2026.8.22__tar.gz
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-2026.7.30 → hackerrank-2026.8.22}/.github/dependabot.yml +5 -0
- hackerrank-2026.8.22/.github/workflows/autofix.yml +37 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/.github/workflows/lint.yml +6 -8
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/.gitignore +3 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/.pre-commit-config.yaml +40 -41
- hackerrank-2026.8.22/.vale.ini +14 -0
- hackerrank-2026.8.22/CHANGELOG.rst +71 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/PKG-INFO +17 -15
- hackerrank-2026.8.22/conftest.py +209 -0
- hackerrank-2026.8.22/newsfragments/change/163.rst +1 -0
- hackerrank-2026.8.22/newsfragments/change/164.rst +1 -0
- hackerrank-2026.8.22/newsfragments/change/167.rst +1 -0
- hackerrank-2026.8.22/newsfragments/change/195.rst +1 -0
- hackerrank-2026.8.22/newsfragments/change/248.rst +1 -0
- hackerrank-2026.8.22/newsfragments/change/249.rst +1 -0
- hackerrank-2026.8.22/newsfragments/change/250.rst +1 -0
- hackerrank-2026.8.22/openapi.json +1 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/pyproject.toml +27 -14
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/spelling_private_dict.txt +11 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/src/hackerrank/_dict_types.py +87 -26
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/src/hackerrank/async_client.py +626 -203
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/src/hackerrank/client.py +608 -202
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/src/hackerrank/exceptions.py +12 -1
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/src/hackerrank/transports.py +14 -14
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/src/hackerrank/types.py +303 -10
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/src/hackerrank.egg-info/PKG-INFO +17 -15
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/src/hackerrank.egg-info/SOURCES.txt +16 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/src/hackerrank.egg-info/requires.txt +16 -14
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/src/hackerrank.egg-info/scm_file_list.json +51 -35
- hackerrank-2026.8.22/src/hackerrank.egg-info/scm_version.json +8 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/tests/conftest.py +35 -6
- hackerrank-2026.8.22/tests/openapi_helpers.py +80 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/tests/test_async_client.py +51 -0
- hackerrank-2026.8.22/tests/test_candidate_search.py +156 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/tests/test_client.py +83 -3
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/tests/test_coverage_gaps.py +224 -3
- hackerrank-2026.8.22/tests/test_documented_request_params.py +452 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/tests/test_endpoints.py +411 -43
- hackerrank-2026.8.22/tests/test_openapi.py +195 -0
- hackerrank-2026.8.22/tests/test_reject_bare_string_lists.py +393 -0
- hackerrank-2026.8.22/tests/test_required_update_bodies.py +285 -0
- hackerrank-2026.8.22/tests/test_response_shapes.py +384 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/tests/test_types.py +241 -1
- hackerrank-2026.7.30/CHANGELOG.rst +0 -37
- hackerrank-2026.7.30/conftest.py +0 -56
- hackerrank-2026.7.30/openapi.json +0 -1
- hackerrank-2026.7.30/src/hackerrank.egg-info/scm_version.json +0 -8
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/.git_archival.txt +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/.gitattributes +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/.github/workflows/ci.yml +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/.github/workflows/dependabot-merge.yml +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/.github/workflows/publish-site.yml +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/.github/workflows/release.yml +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/.prettierrc +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/LICENSE +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/README.rst +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/docs/source/__init__.py +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/docs/source/api-reference.rst +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/docs/source/changelog.rst +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/docs/source/conf.py +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/docs/source/contributing.rst +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/docs/source/index.rst +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/docs/source/openapi-spec.rst +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/docs/source/release-process.rst +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/docs/source/unreleased.rst +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/docs/towncrier_template.rst.jinja +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/newsfragments/.gitkeep +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/setup.cfg +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/src/hackerrank/__init__.py +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/src/hackerrank/py.typed +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/src/hackerrank.egg-info/dependency_links.txt +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/src/hackerrank.egg-info/not-zip-safe +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/src/hackerrank.egg-info/top_level.txt +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/tests/__init__.py +0 -0
- {hackerrank-2026.7.30 → hackerrank-2026.8.22}/zizmor.yml +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: autofix.ci
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: [main]
|
|
7
|
+
push:
|
|
8
|
+
branches: [main]
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
autofix:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v7
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0
|
|
20
|
+
fetch-tags: true
|
|
21
|
+
persist-credentials: false
|
|
22
|
+
|
|
23
|
+
- name: Install uv
|
|
24
|
+
uses: astral-sh/setup-uv@v9.0.0
|
|
25
|
+
|
|
26
|
+
- name: Run fixers
|
|
27
|
+
uses: j178/prek-action@v3.0.0
|
|
28
|
+
with:
|
|
29
|
+
prek-version: 0.4.11
|
|
30
|
+
extra-args: >-
|
|
31
|
+
--all-files --hook-stage pre-commit --no-fail-fast --verbose
|
|
32
|
+
env:
|
|
33
|
+
UV_NO_CACHE: '1'
|
|
34
|
+
UV_PYTHON: 3.13
|
|
35
|
+
|
|
36
|
+
- uses: autofix-ci/action@v1.3.4
|
|
37
|
+
if: always()
|
|
@@ -35,17 +35,15 @@ jobs:
|
|
|
35
35
|
cache-dependency-glob: '**/pyproject.toml'
|
|
36
36
|
|
|
37
37
|
- name: Lint
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
uses: j178/prek-action@v3.0.0
|
|
39
|
+
with:
|
|
40
|
+
prek-version: 0.4.11
|
|
41
|
+
extra-args: >-
|
|
42
|
+
--all-files --hook-stage ${{ matrix.hook-stage }} --verbose
|
|
43
43
|
env:
|
|
44
|
+
UV_NO_CACHE: '1'
|
|
44
45
|
UV_PYTHON: ${{ matrix.python-version }}
|
|
45
46
|
|
|
46
|
-
- uses: pre-commit-ci/lite-action@v1.1.0
|
|
47
|
-
if: always()
|
|
48
|
-
|
|
49
47
|
completion-lint:
|
|
50
48
|
needs: build
|
|
51
49
|
runs-on: ubuntu-latest
|
|
@@ -6,47 +6,6 @@ fail_fast: true
|
|
|
6
6
|
# See https://pre-commit.com for more information
|
|
7
7
|
# See https://pre-commit.com/hooks.html for more hooks
|
|
8
8
|
|
|
9
|
-
ci:
|
|
10
|
-
# We use system Python, with required dependencies specified in pyproject.toml.
|
|
11
|
-
# We therefore cannot use those dependencies in pre-commit CI.
|
|
12
|
-
skip:
|
|
13
|
-
- actionlint
|
|
14
|
-
- sphinx-lint
|
|
15
|
-
- strict-kwargs-fix
|
|
16
|
-
- check-manifest
|
|
17
|
-
- deptry
|
|
18
|
-
- doc8
|
|
19
|
-
- pydocstringformatter
|
|
20
|
-
- docs
|
|
21
|
-
- interrogate
|
|
22
|
-
- interrogate-docs
|
|
23
|
-
- linkcheck
|
|
24
|
-
- mypy
|
|
25
|
-
- mypy-docs
|
|
26
|
-
- pylint
|
|
27
|
-
- pyproject-fmt-fix
|
|
28
|
-
- pyright
|
|
29
|
-
- pyright-docs
|
|
30
|
-
- pyright-verifytypes
|
|
31
|
-
- ty
|
|
32
|
-
- ty-docs
|
|
33
|
-
- pyroma
|
|
34
|
-
- ruff-check-fix
|
|
35
|
-
- ruff-check-fix-docs
|
|
36
|
-
- ruff-format-fix
|
|
37
|
-
- ruff-format-fix-docs
|
|
38
|
-
- shellcheck
|
|
39
|
-
- shellcheck-docs
|
|
40
|
-
- shfmt
|
|
41
|
-
- shfmt-docs
|
|
42
|
-
- spelling
|
|
43
|
-
- vulture
|
|
44
|
-
- vulture-docs
|
|
45
|
-
- yamlfix
|
|
46
|
-
- zizmor
|
|
47
|
-
- pyrefly
|
|
48
|
-
- pyrefly-docs
|
|
49
|
-
|
|
50
9
|
default_install_hook_types: [pre-commit, pre-push]
|
|
51
10
|
|
|
52
11
|
repos:
|
|
@@ -309,6 +268,46 @@ repos:
|
|
|
309
268
|
stages: [pre-commit]
|
|
310
269
|
require_serial: true
|
|
311
270
|
|
|
271
|
+
- id: no-defaults
|
|
272
|
+
name: no-defaults
|
|
273
|
+
entry: uv run --extra=dev no-defaults
|
|
274
|
+
language: python
|
|
275
|
+
types_or: [python]
|
|
276
|
+
additional_dependencies:
|
|
277
|
+
- *uv_version
|
|
278
|
+
stages: [pre-commit]
|
|
279
|
+
require_serial: true
|
|
280
|
+
|
|
281
|
+
# Vale enforces prose style rules, such as banning em dashes, in
|
|
282
|
+
# reStructuredText and Markdown files.
|
|
283
|
+
# The rules come from the ``ai-tells`` package pinned in ``.vale.ini``,
|
|
284
|
+
# which ``vale sync`` downloads into the (gitignored) ``styles``
|
|
285
|
+
# directory.
|
|
286
|
+
# Vale needs ``rst2html`` from Docutils on the ``PATH`` to parse
|
|
287
|
+
# reStructuredText.
|
|
288
|
+
# ``vale sync`` also runs when ``.vale.ini`` changes, so a package
|
|
289
|
+
# bump takes effect without waiting for the next reStructuredText
|
|
290
|
+
# change.
|
|
291
|
+
- id: vale-sync
|
|
292
|
+
name: vale sync
|
|
293
|
+
entry: uv run --extra=dev vale sync
|
|
294
|
+
language: python
|
|
295
|
+
pass_filenames: false
|
|
296
|
+
files: (\.(rst|md)$|^\.vale\.ini$)
|
|
297
|
+
additional_dependencies:
|
|
298
|
+
- *uv_version
|
|
299
|
+
stages: [pre-commit]
|
|
300
|
+
|
|
301
|
+
- id: vale
|
|
302
|
+
name: vale
|
|
303
|
+
entry: uv run --extra=dev vale
|
|
304
|
+
language: python
|
|
305
|
+
types_or: [rst, markdown]
|
|
306
|
+
require_serial: true
|
|
307
|
+
additional_dependencies:
|
|
308
|
+
- *uv_version
|
|
309
|
+
stages: [pre-commit]
|
|
310
|
+
|
|
312
311
|
- id: doc8
|
|
313
312
|
name: doc8
|
|
314
313
|
entry: uv run --extra=dev -m doc8
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
StylesPath = styles
|
|
2
|
+
MinAlertLevel = error
|
|
3
|
+
|
|
4
|
+
Packages = https://github.com/tbhb/vale-ai-tells/releases/download/v1.29.0/ai-tells.zip
|
|
5
|
+
|
|
6
|
+
[*.{rst,md}]
|
|
7
|
+
BasedOnStyles = ai-tells
|
|
8
|
+
|
|
9
|
+
# These rules misclassify established technical, example, or release-note prose
|
|
10
|
+
# in this repository. All other ai-tells rules remain enforced.
|
|
11
|
+
ai-tells.FigurativeLands = NO
|
|
12
|
+
ai-tells.FormalTransitions = NO
|
|
13
|
+
ai-tells.OverusedVocabulary = NO
|
|
14
|
+
ai-tells.VerbTricolon = NO
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Changelog
|
|
2
|
+
=========
|
|
3
|
+
|
|
4
|
+
.. towncrier release notes start
|
|
5
|
+
|
|
6
|
+
2026.08.22
|
|
7
|
+
----------
|
|
8
|
+
|
|
9
|
+
- Accept documented object-form interviewers and candidate_details, preserve
|
|
10
|
+
interview schedule / live fields, and return invite and update response
|
|
11
|
+
bodies from sync and async clients.
|
|
12
|
+
|
|
13
|
+
- Add ``client.candidates.search`` (sync and async) for the global
|
|
14
|
+
candidate-search endpoint, with typed attempt and candidate results.
|
|
15
|
+
|
|
16
|
+
- Expose documented list filters and body fields for interviews,
|
|
17
|
+
questions, templates, and candidate invites on sync and async
|
|
18
|
+
clients.
|
|
19
|
+
|
|
20
|
+
- Reject bare ``str`` values for list-like API fields (``interviewers``,
|
|
21
|
+
``languages``, ``tags``, and related parameters) so they are no longer
|
|
22
|
+
split into character arrays in request bodies.
|
|
23
|
+
|
|
24
|
+
- Refresh ``openapi.json`` from the live HackerRank schema and add a
|
|
25
|
+
semantic drift check that ignores dynamic example timestamps.
|
|
26
|
+
|
|
27
|
+
- Add ``client.interview_templates.explicit_sharing_roles`` with
|
|
28
|
+
``update_access`` and ``remove_access`` for sharing an interview template with
|
|
29
|
+
users, teams or the whole company.
|
|
30
|
+
|
|
31
|
+
Remove the ``team_share`` argument from interview-template ``create`` and
|
|
32
|
+
``update``. HackerRank ignores the parameter and has removed it from the API
|
|
33
|
+
documentation; use ``explicit_sharing_roles`` instead.
|
|
34
|
+
|
|
35
|
+
2026.08.16
|
|
36
|
+
----------
|
|
37
|
+
|
|
38
|
+
No significant changes.
|
|
39
|
+
|
|
40
|
+
2026.07.30
|
|
41
|
+
----------
|
|
42
|
+
|
|
43
|
+
- Align interview-template create and update requests with the current API,
|
|
44
|
+
return updated templates, and parse live string template IDs and integer
|
|
45
|
+
question IDs.
|
|
46
|
+
|
|
47
|
+
2026.07.19
|
|
48
|
+
----------
|
|
49
|
+
|
|
50
|
+
- SCIM v2 requests are now sent to ``https://services.hackerrank.com/scim/v2`` instead of the v3 API host, so ``client.scim`` works out of the box. A new ``scim_base_url`` argument on ``HackerRank`` and ``AsyncHackerRank`` allows overriding it.
|
|
51
|
+
|
|
52
|
+
- Bring the async ``AsyncHackerRank`` client to full parity with the synchronous ``HackerRank`` client, adding all missing operations and aligning method shapes (``ats.codepair``/``ats.codescreen``, ``teams.memberships`` and ``scim.users``/``scim.groups`` sub-namespaces).
|
|
53
|
+
|
|
54
|
+
2026.07.08
|
|
55
|
+
----------
|
|
56
|
+
|
|
57
|
+
No significant changes.
|
|
58
|
+
|
|
59
|
+
2026.05.12.2
|
|
60
|
+
------------
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
2026.05.12.1
|
|
64
|
+
------------
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
2026.05.12
|
|
68
|
+
----------
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
* Initial release.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hackerrank
|
|
3
|
-
Version: 2026.
|
|
3
|
+
Version: 2026.8.22
|
|
4
4
|
Summary: Interact with the HackerRank for Work API.
|
|
5
5
|
Author-email: Adam Dangoor <adamdangoor@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -25,45 +25,47 @@ Requires-Dist: beartype==0.22.9; extra == "dev"
|
|
|
25
25
|
Requires-Dist: check-manifest==0.51; extra == "dev"
|
|
26
26
|
Requires-Dist: deptry==0.25.1; extra == "dev"
|
|
27
27
|
Requires-Dist: doc8==2.0.0; extra == "dev"
|
|
28
|
-
Requires-Dist: doccmd==2026.
|
|
28
|
+
Requires-Dist: doccmd==2026.8.16; extra == "dev"
|
|
29
29
|
Requires-Dist: furo==2025.12.19; extra == "dev"
|
|
30
30
|
Requires-Dist: interrogate==1.7.0; extra == "dev"
|
|
31
|
-
Requires-Dist: mypy[faster-cache]==2.3.
|
|
31
|
+
Requires-Dist: mypy[faster-cache]==2.3.1; extra == "dev"
|
|
32
32
|
Requires-Dist: mypy-strict-kwargs==2026.7.19.1; extra == "dev"
|
|
33
|
-
Requires-Dist:
|
|
34
|
-
Requires-Dist:
|
|
33
|
+
Requires-Dist: no-defaults==2.1.0; extra == "dev"
|
|
34
|
+
Requires-Dist: openapi-mock==2026.8.16; extra == "dev"
|
|
35
|
+
Requires-Dist: prek==0.4.14; extra == "dev"
|
|
35
36
|
Requires-Dist: pydocstringformatter==1.0.0; extra == "dev"
|
|
36
37
|
Requires-Dist: pydocstyle==6.3; extra == "dev"
|
|
37
|
-
Requires-Dist: pygments==2.
|
|
38
|
-
Requires-Dist: pylint[spelling]==4.0.
|
|
38
|
+
Requires-Dist: pygments==2.21.0; extra == "dev"
|
|
39
|
+
Requires-Dist: pylint[spelling]==4.0.7; extra == "dev"
|
|
39
40
|
Requires-Dist: pylint-per-file-ignores==3.2.1; extra == "dev"
|
|
40
|
-
Requires-Dist: pyproject-fmt==2.
|
|
41
|
-
Requires-Dist: pyrefly==1.
|
|
41
|
+
Requires-Dist: pyproject-fmt==2.28.0; extra == "dev"
|
|
42
|
+
Requires-Dist: pyrefly==1.2.0; extra == "dev"
|
|
42
43
|
Requires-Dist: pyright==1.1.411; extra == "dev"
|
|
43
44
|
Requires-Dist: pyroma==5.0.1; extra == "dev"
|
|
44
45
|
Requires-Dist: pytest==9.1.1; extra == "dev"
|
|
45
46
|
Requires-Dist: pytest-asyncio==1.4.0; extra == "dev"
|
|
46
|
-
Requires-Dist: pytest-beartype-tests==2026.
|
|
47
|
+
Requires-Dist: pytest-beartype-tests==2026.8.16; extra == "dev"
|
|
47
48
|
Requires-Dist: pytest-cov==7.1.0; extra == "dev"
|
|
48
49
|
Requires-Dist: pyyaml==6.0.3; extra == "dev"
|
|
49
50
|
Requires-Dist: respx==0.23.1; extra == "dev"
|
|
50
|
-
Requires-Dist: ruff==0.16.
|
|
51
|
+
Requires-Dist: ruff==0.16.3; extra == "dev"
|
|
51
52
|
Requires-Dist: shellcheck-py==0.11.0.1; extra == "dev"
|
|
52
53
|
Requires-Dist: shfmt-py==4.0.0; extra == "dev"
|
|
53
54
|
Requires-Dist: sphinx==9.1.0; extra == "dev"
|
|
54
55
|
Requires-Dist: sphinx-copybutton==0.5.2; extra == "dev"
|
|
55
56
|
Requires-Dist: sphinx-lint==1.0.2; extra == "dev"
|
|
56
57
|
Requires-Dist: sphinx-pyproject==0.3.0; extra == "dev"
|
|
57
|
-
Requires-Dist: sphinx-substitution-extensions==2026.
|
|
58
|
+
Requires-Dist: sphinx-substitution-extensions==2026.8.13.1; extra == "dev"
|
|
58
59
|
Requires-Dist: sphinxcontrib-spelling==8.0.2; extra == "dev"
|
|
59
60
|
Requires-Dist: sphinxcontrib-towncrier==0.5.0a0; extra == "dev"
|
|
60
|
-
Requires-Dist: strict-kwargs==2026.
|
|
61
|
+
Requires-Dist: strict-kwargs==2026.8.16; extra == "dev"
|
|
61
62
|
Requires-Dist: sybil==10.1.0; extra == "dev"
|
|
62
63
|
Requires-Dist: towncrier==25.8.0; extra == "dev"
|
|
63
|
-
Requires-Dist: ty==0.0.
|
|
64
|
+
Requires-Dist: ty==0.0.72; extra == "dev"
|
|
65
|
+
Requires-Dist: vale==3.13.0.0; extra == "dev"
|
|
64
66
|
Requires-Dist: vulture==2.16; extra == "dev"
|
|
65
67
|
Requires-Dist: yamlfix==1.19.1; extra == "dev"
|
|
66
|
-
Requires-Dist: zizmor==1.
|
|
68
|
+
Requires-Dist: zizmor==1.29.0; extra == "dev"
|
|
67
69
|
Provides-Extra: release
|
|
68
70
|
Requires-Dist: check-wheel-contents==0.6.3; extra == "release"
|
|
69
71
|
Requires-Dist: towncrier==25.8.0; extra == "release"
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""Test configuration for hackerrank-api-python."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
import respx
|
|
10
|
+
from openapi_mock import add_openapi_to_respx
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Generator
|
|
14
|
+
|
|
15
|
+
_BASE_URL = "https://www.hackerrank.com"
|
|
16
|
+
_HTTP_METHODS = frozenset(
|
|
17
|
+
{"get", "post", "put", "delete", "patch", "head", "options", "trace"},
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _as_str_keyed_dict(*, value: object) -> dict[str, Any] | None:
|
|
22
|
+
"""Return ``value`` as a ``str``-keyed dict, or ``None``.
|
|
23
|
+
|
|
24
|
+
Uses a JSON round-trip so static checkers see concrete ``Any``
|
|
25
|
+
values rather than unknown dict items from ``isinstance`` narrowing.
|
|
26
|
+
"""
|
|
27
|
+
if not isinstance(value, dict):
|
|
28
|
+
return None
|
|
29
|
+
decoded: Any = json.loads(s=json.dumps(obj=value))
|
|
30
|
+
typed: dict[str, Any] = decoded
|
|
31
|
+
return typed
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _as_object_list(*, value: object) -> list[object] | None:
|
|
35
|
+
"""Return ``value`` as a list of objects, or ``None``."""
|
|
36
|
+
if not isinstance(value, list):
|
|
37
|
+
return None
|
|
38
|
+
decoded: Any = json.loads(s=json.dumps(obj=value))
|
|
39
|
+
typed: list[object] = decoded
|
|
40
|
+
return typed
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def pytest_addoption(parser: pytest.Parser) -> None:
|
|
44
|
+
"""Register command-line options used by this test suite.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
parser: The pytest argument parser.
|
|
48
|
+
"""
|
|
49
|
+
parser.addoption(
|
|
50
|
+
"--run-network",
|
|
51
|
+
action="store_true",
|
|
52
|
+
default=False,
|
|
53
|
+
help="Run tests marked with @pytest.mark.network.",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def pytest_configure(config: pytest.Config) -> None:
|
|
58
|
+
"""Register custom markers.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
config: The pytest config object.
|
|
62
|
+
"""
|
|
63
|
+
config.addinivalue_line(
|
|
64
|
+
name="markers",
|
|
65
|
+
line=(
|
|
66
|
+
"network: tests that fetch live resources "
|
|
67
|
+
"(skipped without --run-network)"
|
|
68
|
+
),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def pytest_collection_modifyitems(
|
|
73
|
+
config: pytest.Config,
|
|
74
|
+
items: list[pytest.Item],
|
|
75
|
+
) -> None:
|
|
76
|
+
"""Skip network tests unless ``--run-network`` is given.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
config: The pytest config object.
|
|
80
|
+
items: Collected test items.
|
|
81
|
+
"""
|
|
82
|
+
if config.getoption(name="--run-network"):
|
|
83
|
+
return
|
|
84
|
+
skip_network = pytest.mark.skip(reason="need --run-network option to run")
|
|
85
|
+
for item in items:
|
|
86
|
+
if "network" in item.keywords:
|
|
87
|
+
item.add_marker(marker=skip_network)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _fix_schema_required(*, schema: dict[str, Any]) -> dict[str, Any]:
|
|
91
|
+
"""Normalize Swagger-style ``required`` flags for OpenAPI 3
|
|
92
|
+
schemas.
|
|
93
|
+
"""
|
|
94
|
+
result: dict[str, Any] = dict(schema)
|
|
95
|
+
props = _as_str_keyed_dict(value=result.get("properties"))
|
|
96
|
+
if props is not None:
|
|
97
|
+
required_names: list[str] = []
|
|
98
|
+
existing_required = _as_object_list(value=result.get("required"))
|
|
99
|
+
if existing_required is not None:
|
|
100
|
+
required_names.extend(
|
|
101
|
+
name for name in existing_required if isinstance(name, str)
|
|
102
|
+
)
|
|
103
|
+
fixed_props: dict[str, Any] = {}
|
|
104
|
+
for prop_name, prop_schema_raw in props.items():
|
|
105
|
+
prop_schema = _as_str_keyed_dict(value=prop_schema_raw)
|
|
106
|
+
if prop_schema is None:
|
|
107
|
+
continue
|
|
108
|
+
fixed_prop = _fix_schema_required(schema=prop_schema)
|
|
109
|
+
if (
|
|
110
|
+
fixed_prop.pop("required", None) is True
|
|
111
|
+
and prop_name not in required_names
|
|
112
|
+
):
|
|
113
|
+
required_names.append(prop_name)
|
|
114
|
+
fixed_props[prop_name] = fixed_prop
|
|
115
|
+
result["properties"] = fixed_props
|
|
116
|
+
if required_names:
|
|
117
|
+
result["required"] = required_names
|
|
118
|
+
elif "required" in result and not isinstance(result["required"], list):
|
|
119
|
+
result.pop("required", None)
|
|
120
|
+
items = _as_str_keyed_dict(value=result.get("items"))
|
|
121
|
+
if items is not None:
|
|
122
|
+
result["items"] = _fix_schema_required(schema=items)
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _migrate_body_parameter(*, operation: dict[str, Any]) -> dict[str, Any]:
|
|
127
|
+
"""Convert Swagger 2 ``in: body`` parameters to OpenAPI 3
|
|
128
|
+
requestBody.
|
|
129
|
+
"""
|
|
130
|
+
result: dict[str, Any] = dict(operation)
|
|
131
|
+
params = _as_object_list(value=result.get("parameters"))
|
|
132
|
+
if params is None:
|
|
133
|
+
return result
|
|
134
|
+
kept: list[object] = []
|
|
135
|
+
body_param: dict[str, Any] | None = None
|
|
136
|
+
for param_raw in params:
|
|
137
|
+
param = _as_str_keyed_dict(value=param_raw)
|
|
138
|
+
if param is not None and param.get("in") == "body":
|
|
139
|
+
body_param = param
|
|
140
|
+
else:
|
|
141
|
+
kept.append(param_raw)
|
|
142
|
+
result["parameters"] = kept
|
|
143
|
+
if body_param is not None and "requestBody" not in result:
|
|
144
|
+
schema_raw = body_param.get("schema", {})
|
|
145
|
+
schema: object = schema_raw
|
|
146
|
+
schema_dict = _as_str_keyed_dict(value=schema_raw)
|
|
147
|
+
if schema_dict is not None:
|
|
148
|
+
schema = _fix_schema_required(schema=schema_dict)
|
|
149
|
+
result["requestBody"] = {
|
|
150
|
+
"required": bool(body_param.get("required", False)),
|
|
151
|
+
"content": {"application/json": {"schema": schema}},
|
|
152
|
+
}
|
|
153
|
+
return result
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _prepare_openapi_spec(*, spec: dict[str, object]) -> dict[str, object]:
|
|
157
|
+
"""Normalize the HackerRank OpenAPI document for mock route
|
|
158
|
+
registration.
|
|
159
|
+
"""
|
|
160
|
+
prepared: dict[str, object] = dict(spec)
|
|
161
|
+
raw_paths = _as_str_keyed_dict(value=prepared.get("paths", {}))
|
|
162
|
+
if raw_paths is None:
|
|
163
|
+
return prepared
|
|
164
|
+
|
|
165
|
+
cleaned_paths: dict[str, dict[str, object]] = {}
|
|
166
|
+
for raw_key, raw_value_obj in raw_paths.items():
|
|
167
|
+
raw_value = _as_str_keyed_dict(value=raw_value_obj)
|
|
168
|
+
if raw_value is None:
|
|
169
|
+
continue
|
|
170
|
+
cleaned = raw_key.split(sep="?", maxsplit=1)[0]
|
|
171
|
+
merged: dict[str, object] = dict(cleaned_paths.get(cleaned, {}))
|
|
172
|
+
for op_key, op_val_obj in raw_value.items():
|
|
173
|
+
op_val = _as_str_keyed_dict(value=op_val_obj)
|
|
174
|
+
if op_key in _HTTP_METHODS and op_val is not None:
|
|
175
|
+
merged[op_key] = _migrate_body_parameter(operation=op_val)
|
|
176
|
+
else:
|
|
177
|
+
merged[op_key] = op_val_obj
|
|
178
|
+
cleaned_paths[cleaned] = merged
|
|
179
|
+
prepared["paths"] = cleaned_paths
|
|
180
|
+
return prepared
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@pytest.fixture(name="mock_hackerrank_api")
|
|
184
|
+
def fixture_mock_hackerrank_api(
|
|
185
|
+
request: pytest.FixtureRequest,
|
|
186
|
+
) -> Generator[respx.MockRouter]:
|
|
187
|
+
"""Provide a respx mock router backed by the OpenAPI spec.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
request: The pytest request, used to locate the
|
|
191
|
+
``openapi.json`` file in the project root.
|
|
192
|
+
|
|
193
|
+
Yields:
|
|
194
|
+
The configured respx mock router.
|
|
195
|
+
"""
|
|
196
|
+
openapi_spec_path = request.config.rootpath / "openapi.json"
|
|
197
|
+
spec_text = openapi_spec_path.read_text(encoding="utf-8")
|
|
198
|
+
openapi_spec: dict[str, object] = json.loads(s=spec_text)
|
|
199
|
+
openapi_spec = _prepare_openapi_spec(spec=openapi_spec)
|
|
200
|
+
with respx.mock(
|
|
201
|
+
base_url=_BASE_URL,
|
|
202
|
+
assert_all_called=False,
|
|
203
|
+
) as mock_router:
|
|
204
|
+
add_openapi_to_respx(
|
|
205
|
+
mock_obj=mock_router,
|
|
206
|
+
spec=openapi_spec,
|
|
207
|
+
base_url=_BASE_URL,
|
|
208
|
+
)
|
|
209
|
+
yield mock_router
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SCIM user and group PATCH methods now parse the documented message acknowledgement instead of treating the response as a full resource.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SCIM group PATCH methods now parse the documented message acknowledgement instead of treating the response as a full group.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SCIM pagination preserves an explicit ``startIndex`` of ``0`` instead of rewriting it to ``1``.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
``generate_codestubs`` now requires a request body, matching the API contract.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Custom transports are only replaced by the default when ``transport`` is ``None``, so falsy callables are preserved.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Unexpected HTTP 3xx redirect responses now raise ``RedirectError`` instead of being treated as successful API responses.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Trailing slashes on custom ``base_url`` and ``scim_base_url`` values are stripped so request paths are not double-slashed.
|