hackerrank 2026.8.28__tar.gz → 2026.9.7__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.8.28 → hackerrank-2026.9.7}/CHANGELOG.rst +6 -0
- {hackerrank-2026.8.28/src/hackerrank.egg-info → hackerrank-2026.9.7}/PKG-INFO +41 -12
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/README.rst +27 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/docs/source/conf.py +2 -0
- hackerrank-2026.9.7/docs/source/index.rst +75 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/pyproject.toml +22 -13
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/spelling_private_dict.txt +1 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/src/hackerrank/async_client.py +7 -2
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/src/hackerrank/client.py +4 -2
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/src/hackerrank/transports.py +172 -2
- {hackerrank-2026.8.28 → hackerrank-2026.9.7/src/hackerrank.egg-info}/PKG-INFO +41 -12
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/src/hackerrank.egg-info/requires.txt +13 -11
- hackerrank-2026.9.7/src/hackerrank.egg-info/scm_version.json +8 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/test_async_client.py +72 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/test_client.py +65 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/test_retries.py +47 -0
- hackerrank-2026.8.28/docs/source/index.rst +0 -48
- hackerrank-2026.8.28/src/hackerrank.egg-info/scm_version.json +0 -8
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/.git_archival.txt +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/.gitattributes +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/.github/dependabot.yml +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/.github/workflows/autofix.yml +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/.github/workflows/ci.yml +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/.github/workflows/dependabot-merge.yml +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/.github/workflows/lint.yml +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/.github/workflows/publish-site.yml +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/.github/workflows/release.yml +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/.gitignore +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/.prettierrc +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/.vale.ini +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/LICENSE +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/conftest.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/docs/source/__init__.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/docs/source/api-reference.rst +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/docs/source/changelog.rst +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/docs/source/contributing.rst +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/docs/source/openapi-spec.rst +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/docs/source/release-process.rst +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/docs/source/unreleased.rst +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/docs/towncrier_template.rst.jinja +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/newsfragments/.gitkeep +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/openapi.json +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/prek.toml +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/setup.cfg +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/src/hackerrank/__init__.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/src/hackerrank/_dict_types.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/src/hackerrank/_retries.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/src/hackerrank/exceptions.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/src/hackerrank/py.typed +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/src/hackerrank/types.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/src/hackerrank.egg-info/SOURCES.txt +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/src/hackerrank.egg-info/dependency_links.txt +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/src/hackerrank.egg-info/not-zip-safe +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/src/hackerrank.egg-info/scm_file_list.json +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/src/hackerrank.egg-info/top_level.txt +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/__init__.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/conftest.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/openapi_helpers.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/test_candidate_search.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/test_coverage_gaps.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/test_documented_request_params.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/test_endpoints.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/test_newsfragments.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/test_openapi.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/test_reject_bare_string_lists.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/test_required_update_bodies.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/test_response_shapes.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/test_retry_safety_docs.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/tests/test_types.py +0 -0
- {hackerrank-2026.8.28 → hackerrank-2026.9.7}/zizmor.yml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hackerrank
|
|
3
|
-
Version: 2026.
|
|
3
|
+
Version: 2026.9.7
|
|
4
4
|
Summary: Interact with the HackerRank for Work API.
|
|
5
5
|
Author-email: Adam Dangoor <adamdangoor@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -19,26 +19,27 @@ Description-Content-Type: text/x-rst
|
|
|
19
19
|
License-File: LICENSE
|
|
20
20
|
Requires-Dist: beartype>=0.22.9
|
|
21
21
|
Requires-Dist: httpx>=0.28.0
|
|
22
|
+
Requires-Dist: httpx2>=2.12.0
|
|
22
23
|
Provides-Extra: dev
|
|
23
24
|
Requires-Dist: actionlint-py==1.7.12.24; extra == "dev"
|
|
24
25
|
Requires-Dist: beartype==0.22.9; extra == "dev"
|
|
25
26
|
Requires-Dist: check-manifest==0.51; extra == "dev"
|
|
26
27
|
Requires-Dist: deptry==0.25.1; extra == "dev"
|
|
27
28
|
Requires-Dist: doc8==2.0.0; extra == "dev"
|
|
28
|
-
Requires-Dist: doccmd==2026.
|
|
29
|
+
Requires-Dist: doccmd==2026.9.1; extra == "dev"
|
|
29
30
|
Requires-Dist: furo==2025.12.19; extra == "dev"
|
|
30
31
|
Requires-Dist: interrogate==1.7.0; extra == "dev"
|
|
31
32
|
Requires-Dist: mypy[faster-cache]==2.3.1; extra == "dev"
|
|
32
33
|
Requires-Dist: mypy-strict-kwargs==2026.8.25.1; extra == "dev"
|
|
33
|
-
Requires-Dist: no-defaults==
|
|
34
|
+
Requires-Dist: no-defaults==2026.9.1; extra == "dev"
|
|
34
35
|
Requires-Dist: openapi-mock==2026.8.16; extra == "dev"
|
|
35
|
-
Requires-Dist: prek==0.
|
|
36
|
+
Requires-Dist: prek==0.5.2; extra == "dev"
|
|
36
37
|
Requires-Dist: pydocstringformatter==1.0.0; extra == "dev"
|
|
37
38
|
Requires-Dist: pydocstyle==6.3; extra == "dev"
|
|
38
39
|
Requires-Dist: pygments==2.21.0; extra == "dev"
|
|
39
|
-
Requires-Dist: pylint[spelling]==4.0.
|
|
40
|
+
Requires-Dist: pylint[spelling]==4.0.8; extra == "dev"
|
|
40
41
|
Requires-Dist: pylint-per-file-ignores==3.2.1; extra == "dev"
|
|
41
|
-
Requires-Dist: pyproject-fmt==2.
|
|
42
|
+
Requires-Dist: pyproject-fmt==2.29.3; extra == "dev"
|
|
42
43
|
Requires-Dist: pyrefly==1.2.0; extra == "dev"
|
|
43
44
|
Requires-Dist: pyright==1.1.411; extra == "dev"
|
|
44
45
|
Requires-Dist: pyroma==5.0.1; extra == "dev"
|
|
@@ -46,11 +47,12 @@ Requires-Dist: pytest==9.1.1; extra == "dev"
|
|
|
46
47
|
Requires-Dist: pytest-asyncio==1.4.0; extra == "dev"
|
|
47
48
|
Requires-Dist: pytest-beartype-tests==2026.8.16; extra == "dev"
|
|
48
49
|
Requires-Dist: pytest-cov==7.1.0; extra == "dev"
|
|
50
|
+
Requires-Dist: pytest-httpx2==1.0.0; extra == "dev"
|
|
49
51
|
Requires-Dist: pyyaml==6.0.3; extra == "dev"
|
|
50
52
|
Requires-Dist: respx==0.23.1; extra == "dev"
|
|
51
|
-
Requires-Dist: ruff==0.16.
|
|
53
|
+
Requires-Dist: ruff==0.16.6; extra == "dev"
|
|
52
54
|
Requires-Dist: shellcheck-py==0.11.0.1; extra == "dev"
|
|
53
|
-
Requires-Dist: shfmt-py==4.
|
|
55
|
+
Requires-Dist: shfmt-py==4.1.0; extra == "dev"
|
|
54
56
|
Requires-Dist: sphinx==9.1.0; extra == "dev"
|
|
55
57
|
Requires-Dist: sphinx-copybutton==0.5.2; extra == "dev"
|
|
56
58
|
Requires-Dist: sphinx-lint==1.0.2; extra == "dev"
|
|
@@ -58,14 +60,14 @@ Requires-Dist: sphinx-pyproject==0.3.0; extra == "dev"
|
|
|
58
60
|
Requires-Dist: sphinx-substitution-extensions==2026.8.13.1; extra == "dev"
|
|
59
61
|
Requires-Dist: sphinxcontrib-spelling==8.0.2; extra == "dev"
|
|
60
62
|
Requires-Dist: sphinxcontrib-towncrier==0.5.0a0; extra == "dev"
|
|
61
|
-
Requires-Dist: strict-kwargs==2026.8.
|
|
63
|
+
Requires-Dist: strict-kwargs==2026.8.28.post2; extra == "dev"
|
|
62
64
|
Requires-Dist: sybil==10.1.0; extra == "dev"
|
|
63
65
|
Requires-Dist: towncrier==25.8.0; extra == "dev"
|
|
64
|
-
Requires-Dist: ty==0.0.
|
|
65
|
-
Requires-Dist: vale==3.
|
|
66
|
+
Requires-Dist: ty==0.0.78; extra == "dev"
|
|
67
|
+
Requires-Dist: vale==3.20.0.0; extra == "dev"
|
|
66
68
|
Requires-Dist: vulture==2.16; extra == "dev"
|
|
67
69
|
Requires-Dist: yamlfix==1.19.1; extra == "dev"
|
|
68
|
-
Requires-Dist: zizmor==1.
|
|
70
|
+
Requires-Dist: zizmor==1.30.0; extra == "dev"
|
|
69
71
|
Provides-Extra: release
|
|
70
72
|
Requires-Dist: check-wheel-contents==0.6.3; extra == "release"
|
|
71
73
|
Requires-Dist: towncrier==25.8.0; extra == "release"
|
|
@@ -111,6 +113,33 @@ pass it as ``api_key``:
|
|
|
111
113
|
|
|
112
114
|
.. _HackerRank for Work tokens page: https://www.hackerrank.com/work/settings/token
|
|
113
115
|
|
|
116
|
+
HTTPX2 transports
|
|
117
|
+
-----------------
|
|
118
|
+
|
|
119
|
+
HTTPX remains the default client family. To opt in to HTTPX2, construct the
|
|
120
|
+
HTTPX2 transport explicitly and pass it to the client. The standard
|
|
121
|
+
``hackerrank`` installation includes both client families.
|
|
122
|
+
|
|
123
|
+
.. code-block:: python
|
|
124
|
+
|
|
125
|
+
"""Configure HackerRank to use HTTPX2."""
|
|
126
|
+
|
|
127
|
+
import httpx2
|
|
128
|
+
|
|
129
|
+
from hackerrank.client import HackerRank
|
|
130
|
+
from hackerrank.transports import HTTPX2Transport
|
|
131
|
+
|
|
132
|
+
transport = HTTPX2Transport(timeout=httpx2.Timeout(timeout=10))
|
|
133
|
+
with HackerRank(api_key="your-api-key", transport=transport) as client:
|
|
134
|
+
tests = client.tests.list()
|
|
135
|
+
|
|
136
|
+
Use ``AsyncHTTPX2Transport`` with ``AsyncHackerRank`` for asynchronous calls.
|
|
137
|
+
The ``timeout`` argument accepts an HTTPX2 timeout object or a number of
|
|
138
|
+
seconds; do not pass an equivalent ``httpx`` object across the package
|
|
139
|
+
boundary. Existing HTTPX users do not need to change anything. Migrating to
|
|
140
|
+
HTTPX2 only requires selecting the new transport; HackerRank client methods
|
|
141
|
+
and returned models are unchanged.
|
|
142
|
+
|
|
114
143
|
Full Documentation
|
|
115
144
|
------------------
|
|
116
145
|
|
|
@@ -38,6 +38,33 @@ pass it as ``api_key``:
|
|
|
38
38
|
|
|
39
39
|
.. _HackerRank for Work tokens page: https://www.hackerrank.com/work/settings/token
|
|
40
40
|
|
|
41
|
+
HTTPX2 transports
|
|
42
|
+
-----------------
|
|
43
|
+
|
|
44
|
+
HTTPX remains the default client family. To opt in to HTTPX2, construct the
|
|
45
|
+
HTTPX2 transport explicitly and pass it to the client. The standard
|
|
46
|
+
``hackerrank`` installation includes both client families.
|
|
47
|
+
|
|
48
|
+
.. code-block:: python
|
|
49
|
+
|
|
50
|
+
"""Configure HackerRank to use HTTPX2."""
|
|
51
|
+
|
|
52
|
+
import httpx2
|
|
53
|
+
|
|
54
|
+
from hackerrank.client import HackerRank
|
|
55
|
+
from hackerrank.transports import HTTPX2Transport
|
|
56
|
+
|
|
57
|
+
transport = HTTPX2Transport(timeout=httpx2.Timeout(timeout=10))
|
|
58
|
+
with HackerRank(api_key="your-api-key", transport=transport) as client:
|
|
59
|
+
tests = client.tests.list()
|
|
60
|
+
|
|
61
|
+
Use ``AsyncHTTPX2Transport`` with ``AsyncHackerRank`` for asynchronous calls.
|
|
62
|
+
The ``timeout`` argument accepts an HTTPX2 timeout object or a number of
|
|
63
|
+
seconds; do not pass an equivalent ``httpx`` object across the package
|
|
64
|
+
boundary. Existing HTTPX users do not need to change anything. Migrating to
|
|
65
|
+
HTTPX2 only requires selecting the new transport; HackerRank client methods
|
|
66
|
+
and returned models are unchanged.
|
|
67
|
+
|
|
41
68
|
Full Documentation
|
|
42
69
|
------------------
|
|
43
70
|
|
|
@@ -82,6 +82,8 @@ nitpick_ignore: list[tuple[str, str]] = [
|
|
|
82
82
|
# ``httpx`` does not publish a Sphinx object inventory, so its
|
|
83
83
|
# types cannot be cross-referenced.
|
|
84
84
|
("py:class", "httpx.Timeout"),
|
|
85
|
+
# ``httpx2`` does not expose this class in its Sphinx inventory.
|
|
86
|
+
("py:class", "httpx2.Timeout"),
|
|
85
87
|
]
|
|
86
88
|
nitpick_ignore_regex = [
|
|
87
89
|
(r"py:class", r"hackerrank\._dict_types\..*"),
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
|project|
|
|
2
|
+
=========
|
|
3
|
+
|
|
4
|
+
Installation
|
|
5
|
+
------------
|
|
6
|
+
|
|
7
|
+
.. code-block:: console
|
|
8
|
+
|
|
9
|
+
$ pip install hackerrank
|
|
10
|
+
|
|
11
|
+
This is tested on Python |minimum-python-version|\+.
|
|
12
|
+
|
|
13
|
+
Usage
|
|
14
|
+
-----
|
|
15
|
+
|
|
16
|
+
Generate an API token from the `HackerRank for Work tokens page`_ and pass
|
|
17
|
+
it to :class:`hackerrank.client.HackerRank`:
|
|
18
|
+
|
|
19
|
+
.. code-block:: python
|
|
20
|
+
|
|
21
|
+
"""Example usage."""
|
|
22
|
+
|
|
23
|
+
import sys
|
|
24
|
+
|
|
25
|
+
from hackerrank.client import HackerRank
|
|
26
|
+
|
|
27
|
+
client = HackerRank(api_key="your-api-key")
|
|
28
|
+
for test in client.tests.list().data:
|
|
29
|
+
sys.stdout.write(test.name)
|
|
30
|
+
interview = client.interviews.create(title="My Interview")
|
|
31
|
+
sys.stdout.write(interview.url or "")
|
|
32
|
+
|
|
33
|
+
HTTPX2 transports
|
|
34
|
+
-----------------
|
|
35
|
+
|
|
36
|
+
HTTPX remains the default client family. To opt in to HTTPX2, construct the
|
|
37
|
+
HTTPX2 transport explicitly and pass it to the client. The standard
|
|
38
|
+
``hackerrank`` installation includes both client families.
|
|
39
|
+
|
|
40
|
+
.. code-block:: python
|
|
41
|
+
|
|
42
|
+
"""Configure HackerRank to use HTTPX2."""
|
|
43
|
+
|
|
44
|
+
import httpx2
|
|
45
|
+
|
|
46
|
+
from hackerrank.client import HackerRank
|
|
47
|
+
from hackerrank.transports import HTTPX2Transport
|
|
48
|
+
|
|
49
|
+
transport = HTTPX2Transport(timeout=httpx2.Timeout(timeout=10))
|
|
50
|
+
with HackerRank(api_key="your-api-key", transport=transport) as client:
|
|
51
|
+
tests = client.tests.list()
|
|
52
|
+
|
|
53
|
+
Use ``AsyncHTTPX2Transport`` with ``AsyncHackerRank`` for asynchronous calls.
|
|
54
|
+
The ``timeout`` argument accepts an HTTPX2 timeout object or a number of
|
|
55
|
+
seconds; do not pass an equivalent ``httpx`` object across the package
|
|
56
|
+
boundary. Existing HTTPX users do not need to change anything. Migrating to
|
|
57
|
+
HTTPX2 only requires selecting the new transport; HackerRank client methods
|
|
58
|
+
and returned models are unchanged.
|
|
59
|
+
|
|
60
|
+
See the :doc:`api-reference` for full usage details.
|
|
61
|
+
|
|
62
|
+
.. _HackerRank for Work tokens page: https://www.hackerrank.com/work/settings/token
|
|
63
|
+
|
|
64
|
+
Reference
|
|
65
|
+
---------
|
|
66
|
+
|
|
67
|
+
.. toctree::
|
|
68
|
+
:maxdepth: 3
|
|
69
|
+
|
|
70
|
+
api-reference
|
|
71
|
+
openapi-spec
|
|
72
|
+
contributing
|
|
73
|
+
release-process
|
|
74
|
+
unreleased
|
|
75
|
+
changelog
|
|
@@ -34,6 +34,7 @@ dynamic = [
|
|
|
34
34
|
dependencies = [
|
|
35
35
|
"beartype>=0.22.9",
|
|
36
36
|
"httpx>=0.28.0",
|
|
37
|
+
"httpx2>=2.12.0",
|
|
37
38
|
]
|
|
38
39
|
optional-dependencies.dev = [
|
|
39
40
|
"actionlint-py==1.7.12.24",
|
|
@@ -41,20 +42,20 @@ optional-dependencies.dev = [
|
|
|
41
42
|
"check-manifest==0.51",
|
|
42
43
|
"deptry==0.25.1",
|
|
43
44
|
"doc8==2.0.0",
|
|
44
|
-
"doccmd==2026.
|
|
45
|
+
"doccmd==2026.9.1",
|
|
45
46
|
"furo==2025.12.19",
|
|
46
47
|
"interrogate==1.7.0",
|
|
47
48
|
"mypy[faster-cache]==2.3.1",
|
|
48
49
|
"mypy-strict-kwargs==2026.8.25.1",
|
|
49
|
-
"no-defaults==
|
|
50
|
+
"no-defaults==2026.9.1",
|
|
50
51
|
"openapi-mock==2026.8.16",
|
|
51
|
-
"prek==0.
|
|
52
|
+
"prek==0.5.2",
|
|
52
53
|
"pydocstringformatter==1.0.0",
|
|
53
54
|
"pydocstyle==6.3",
|
|
54
55
|
"pygments==2.21.0",
|
|
55
|
-
"pylint[spelling]==4.0.
|
|
56
|
+
"pylint[spelling]==4.0.8",
|
|
56
57
|
"pylint-per-file-ignores==3.2.1",
|
|
57
|
-
"pyproject-fmt==2.
|
|
58
|
+
"pyproject-fmt==2.29.3",
|
|
58
59
|
"pyrefly==1.2.0",
|
|
59
60
|
"pyright==1.1.411",
|
|
60
61
|
"pyroma==5.0.1",
|
|
@@ -62,14 +63,15 @@ optional-dependencies.dev = [
|
|
|
62
63
|
"pytest-asyncio==1.4.0",
|
|
63
64
|
"pytest-beartype-tests==2026.8.16",
|
|
64
65
|
"pytest-cov==7.1.0",
|
|
66
|
+
"pytest-httpx2==1.0.0",
|
|
65
67
|
"pyyaml==6.0.3",
|
|
66
68
|
"respx==0.23.1",
|
|
67
|
-
"ruff==0.16.
|
|
69
|
+
"ruff==0.16.6",
|
|
68
70
|
# We add shellcheck-py not only for shell scripts and shell code blocks,
|
|
69
71
|
# but also because having it installed means that ``actionlint-py`` will
|
|
70
72
|
# use it to lint shell commands in GitHub workflow files.
|
|
71
73
|
"shellcheck-py==0.11.0.1",
|
|
72
|
-
"shfmt-py==4.
|
|
74
|
+
"shfmt-py==4.1.0",
|
|
73
75
|
"sphinx==9.1.0",
|
|
74
76
|
"sphinx-copybutton==0.5.2",
|
|
75
77
|
"sphinx-lint==1.0.2",
|
|
@@ -79,14 +81,14 @@ optional-dependencies.dev = [
|
|
|
79
81
|
# ``sphinxcontrib-towncrier`` renders unreleased news fragments
|
|
80
82
|
# into docs/source/unreleased.rst during Sphinx builds.
|
|
81
83
|
"sphinxcontrib-towncrier==0.5.0a0",
|
|
82
|
-
"strict-kwargs==2026.8.
|
|
84
|
+
"strict-kwargs==2026.8.28.post2",
|
|
83
85
|
"sybil==10.1.0",
|
|
84
86
|
"towncrier==25.8.0",
|
|
85
|
-
"ty==0.0.
|
|
86
|
-
"vale==3.
|
|
87
|
+
"ty==0.0.78",
|
|
88
|
+
"vale==3.20.0.0",
|
|
87
89
|
"vulture==2.16",
|
|
88
90
|
"yamlfix==1.19.1",
|
|
89
|
-
"zizmor==1.
|
|
91
|
+
"zizmor==1.30.0",
|
|
90
92
|
]
|
|
91
93
|
optional-dependencies.release = [ "check-wheel-contents==0.6.3", "towncrier==25.8.0" ]
|
|
92
94
|
urls.Documentation = "https://adamtheturtle.github.io/hackerrank/"
|
|
@@ -149,7 +151,9 @@ lint.unfixable = [
|
|
|
149
151
|
"ERA001",
|
|
150
152
|
]
|
|
151
153
|
lint.external = [ "NOD" ]
|
|
152
|
-
lint.flake8-tidy-imports.banned-api."typing.cast".msg = "
|
|
154
|
+
lint.flake8-tidy-imports.banned-api."typing.cast".msg = """\
|
|
155
|
+
typing.cast is banned: use explicit type narrowing or a typed variable instead.\
|
|
156
|
+
"""
|
|
153
157
|
lint.pydocstyle.convention = "google"
|
|
154
158
|
|
|
155
159
|
[tool.pylint]
|
|
@@ -537,8 +541,8 @@ plugins = [
|
|
|
537
541
|
]
|
|
538
542
|
|
|
539
543
|
[tool.pyrefly]
|
|
540
|
-
errors.non-exhaustive-match = "error"
|
|
541
544
|
search-path = [ "." ]
|
|
545
|
+
errors.non-exhaustive-match = "error"
|
|
542
546
|
|
|
543
547
|
[tool.pyright]
|
|
544
548
|
typeCheckingMode = "strict"
|
|
@@ -574,6 +578,11 @@ issue_format = "#{issue}"
|
|
|
574
578
|
# version such as ``2026.05.18`` underlined with ``-`` matches every
|
|
575
579
|
# pre-towncrier entry in CHANGELOG.rst.
|
|
576
580
|
underlines = [ "-", "~", "^" ]
|
|
581
|
+
# Setting ``ignore`` makes ``towncrier build`` fail on any file in
|
|
582
|
+
# the fragment directory whose suffix is not a declared fragment
|
|
583
|
+
# type, instead of silently dropping it. Only the listed
|
|
584
|
+
# non-fragment files are allowed through.
|
|
585
|
+
ignore = [ ".gitkeep" ]
|
|
577
586
|
type = [
|
|
578
587
|
# A single, unnamed fragment type keeps the assembled output as one
|
|
579
588
|
# flat bullet list, matching the historical changelog (which never
|
|
@@ -8,6 +8,7 @@ from types import TracebackType
|
|
|
8
8
|
from typing import Any, BinaryIO, Self
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
|
+
import httpx2
|
|
11
12
|
from beartype import beartype
|
|
12
13
|
|
|
13
14
|
from hackerrank._retries import (
|
|
@@ -18,6 +19,7 @@ from hackerrank._retries import (
|
|
|
18
19
|
)
|
|
19
20
|
from hackerrank.exceptions import HackerRankError
|
|
20
21
|
from hackerrank.transports import (
|
|
22
|
+
AsyncHTTPX2Transport,
|
|
21
23
|
AsyncHTTPXTransport,
|
|
22
24
|
AsyncTransport,
|
|
23
25
|
TransportResponse,
|
|
@@ -318,7 +320,7 @@ class _AsyncNamespace:
|
|
|
318
320
|
json=json,
|
|
319
321
|
files=files,
|
|
320
322
|
)
|
|
321
|
-
except httpx.TransportError as exc:
|
|
323
|
+
except (httpx.TransportError, httpx2.TransportError) as exc:
|
|
322
324
|
if not (retriable and rewind_files(files=files)):
|
|
323
325
|
raise
|
|
324
326
|
headers = None
|
|
@@ -3290,7 +3292,10 @@ class AsyncHackerRank:
|
|
|
3290
3292
|
)
|
|
3291
3293
|
self._owned_transport = (
|
|
3292
3294
|
resolved_transport
|
|
3293
|
-
if isinstance(
|
|
3295
|
+
if isinstance(
|
|
3296
|
+
resolved_transport,
|
|
3297
|
+
(AsyncHTTPXTransport, AsyncHTTPX2Transport),
|
|
3298
|
+
)
|
|
3294
3299
|
else None
|
|
3295
3300
|
)
|
|
3296
3301
|
|
|
@@ -8,6 +8,7 @@ from types import TracebackType
|
|
|
8
8
|
from typing import Any, BinaryIO, Self
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
|
+
import httpx2
|
|
11
12
|
from beartype import beartype
|
|
12
13
|
|
|
13
14
|
from hackerrank._retries import (
|
|
@@ -18,6 +19,7 @@ from hackerrank._retries import (
|
|
|
18
19
|
)
|
|
19
20
|
from hackerrank.exceptions import HackerRankError
|
|
20
21
|
from hackerrank.transports import (
|
|
22
|
+
HTTPX2Transport,
|
|
21
23
|
HTTPXTransport,
|
|
22
24
|
Transport,
|
|
23
25
|
TransportResponse,
|
|
@@ -140,7 +142,7 @@ class _Namespace:
|
|
|
140
142
|
json=json,
|
|
141
143
|
files=files,
|
|
142
144
|
)
|
|
143
|
-
except httpx.TransportError as exc:
|
|
145
|
+
except (httpx.TransportError, httpx2.TransportError) as exc:
|
|
144
146
|
if not (retriable and rewind_files(files=files)):
|
|
145
147
|
raise
|
|
146
148
|
headers = None
|
|
@@ -3278,7 +3280,7 @@ class HackerRank:
|
|
|
3278
3280
|
headers=headers,
|
|
3279
3281
|
retries=retries,
|
|
3280
3282
|
)
|
|
3281
|
-
if isinstance(resolved_transport, HTTPXTransport):
|
|
3283
|
+
if isinstance(resolved_transport, (HTTPXTransport, HTTPX2Transport)):
|
|
3282
3284
|
self._close = resolved_transport.close
|
|
3283
3285
|
else:
|
|
3284
3286
|
self._close = lambda: None
|
|
@@ -8,6 +8,7 @@ from types import TracebackType
|
|
|
8
8
|
from typing import Any, Protocol, Self, runtime_checkable
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
|
+
import httpx2
|
|
11
12
|
from beartype import beartype
|
|
12
13
|
|
|
13
14
|
from hackerrank.types import JSONValue
|
|
@@ -15,8 +16,8 @@ from hackerrank.types import JSONValue
|
|
|
15
16
|
DEFAULT_TIMEOUT_SECONDS = 60.0
|
|
16
17
|
"""The default timeout, in seconds, for the built-in transports.
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
which carry large payloads, such as project zip uploads.
|
|
19
|
+
The HTTP client libraries default to 5 seconds, which is too short for
|
|
20
|
+
endpoints which carry large payloads, such as project zip uploads.
|
|
20
21
|
"""
|
|
21
22
|
|
|
22
23
|
|
|
@@ -194,6 +195,92 @@ class HTTPXTransport:
|
|
|
194
195
|
)
|
|
195
196
|
|
|
196
197
|
|
|
198
|
+
@beartype
|
|
199
|
+
class HTTPX2Transport:
|
|
200
|
+
"""HTTP transport using the ``httpx2`` library.
|
|
201
|
+
|
|
202
|
+
This opt-in transport uses a shared ``httpx2.Client`` for connection
|
|
203
|
+
pooling. Configuration objects must come from ``httpx2``, not ``httpx``.
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
def __init__(
|
|
207
|
+
self,
|
|
208
|
+
*,
|
|
209
|
+
# ``int`` is redundant to a type checker, which reads ``float``
|
|
210
|
+
# as "int or float", but ``beartype`` checks the annotation
|
|
211
|
+
# literally at runtime and would reject ``timeout=60``.
|
|
212
|
+
timeout: httpx2.Timeout | float | int = DEFAULT_TIMEOUT_SECONDS, # noqa: PYI041
|
|
213
|
+
) -> None:
|
|
214
|
+
"""Create a new HTTPX2 transport.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
timeout: The timeout to use for every request, either
|
|
218
|
+
a number of seconds or an ``httpx2.Timeout``.
|
|
219
|
+
Defaults to
|
|
220
|
+
:data:`DEFAULT_TIMEOUT_SECONDS` seconds.
|
|
221
|
+
"""
|
|
222
|
+
self._client = httpx2.Client(timeout=timeout)
|
|
223
|
+
|
|
224
|
+
def close(self) -> None:
|
|
225
|
+
"""Close the underlying HTTP client."""
|
|
226
|
+
self._client.close()
|
|
227
|
+
|
|
228
|
+
def __enter__(self) -> Self:
|
|
229
|
+
"""Enter the context manager.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
This transport instance.
|
|
233
|
+
"""
|
|
234
|
+
return self
|
|
235
|
+
|
|
236
|
+
def __exit__(
|
|
237
|
+
self,
|
|
238
|
+
_exc_type: type[BaseException] | None,
|
|
239
|
+
_exc_val: BaseException | None,
|
|
240
|
+
_exc_tb: TracebackType | None,
|
|
241
|
+
) -> None:
|
|
242
|
+
"""Exit the context manager and close the client."""
|
|
243
|
+
self.close()
|
|
244
|
+
|
|
245
|
+
def __call__(
|
|
246
|
+
self,
|
|
247
|
+
*,
|
|
248
|
+
method: str,
|
|
249
|
+
url: str,
|
|
250
|
+
headers: dict[str, str],
|
|
251
|
+
params: dict[str, str | int] | None,
|
|
252
|
+
json: Mapping[str, JSONValue] | None,
|
|
253
|
+
files: Mapping[str, Any] | None,
|
|
254
|
+
) -> TransportResponse:
|
|
255
|
+
"""Make an HTTP request using ``httpx2``.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
method: The HTTP method.
|
|
259
|
+
url: The full URL.
|
|
260
|
+
headers: Request headers.
|
|
261
|
+
params: Query parameters.
|
|
262
|
+
json: A JSON-serialisable body.
|
|
263
|
+
files: Files to send as multipart form-data.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
A ``TransportResponse`` populated from the httpx2
|
|
267
|
+
response.
|
|
268
|
+
"""
|
|
269
|
+
response = self._client.request(
|
|
270
|
+
method=method,
|
|
271
|
+
url=url,
|
|
272
|
+
headers=headers,
|
|
273
|
+
params=params,
|
|
274
|
+
json=json,
|
|
275
|
+
files=files,
|
|
276
|
+
)
|
|
277
|
+
return TransportResponse(
|
|
278
|
+
status_code=response.status_code,
|
|
279
|
+
headers=dict(response.headers),
|
|
280
|
+
content=response.content,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
197
284
|
@runtime_checkable
|
|
198
285
|
class AsyncTransport(Protocol):
|
|
199
286
|
"""Protocol for async HTTP transports."""
|
|
@@ -306,3 +393,86 @@ class AsyncHTTPXTransport:
|
|
|
306
393
|
headers=dict(response.headers),
|
|
307
394
|
content=response.content,
|
|
308
395
|
)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
@beartype
|
|
399
|
+
class AsyncHTTPX2Transport:
|
|
400
|
+
"""Async HTTP transport using the ``httpx2`` library."""
|
|
401
|
+
|
|
402
|
+
def __init__(
|
|
403
|
+
self,
|
|
404
|
+
*,
|
|
405
|
+
# ``int`` is redundant to a type checker, which reads ``float``
|
|
406
|
+
# as "int or float", but ``beartype`` checks the annotation
|
|
407
|
+
# literally at runtime and would reject ``timeout=60``.
|
|
408
|
+
timeout: httpx2.Timeout | float | int = DEFAULT_TIMEOUT_SECONDS, # noqa: PYI041
|
|
409
|
+
) -> None:
|
|
410
|
+
"""Create a new async HTTPX2 transport.
|
|
411
|
+
|
|
412
|
+
Args:
|
|
413
|
+
timeout: The timeout to use for every request, either
|
|
414
|
+
a number of seconds or an ``httpx2.Timeout``.
|
|
415
|
+
Defaults to
|
|
416
|
+
:data:`DEFAULT_TIMEOUT_SECONDS` seconds.
|
|
417
|
+
"""
|
|
418
|
+
self._client = httpx2.AsyncClient(timeout=timeout)
|
|
419
|
+
|
|
420
|
+
async def aclose(self) -> None:
|
|
421
|
+
"""Close the underlying async HTTP client."""
|
|
422
|
+
await self._client.aclose()
|
|
423
|
+
|
|
424
|
+
async def __aenter__(self) -> Self:
|
|
425
|
+
"""Enter the async context manager.
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
This transport instance.
|
|
429
|
+
"""
|
|
430
|
+
return self
|
|
431
|
+
|
|
432
|
+
async def __aexit__(
|
|
433
|
+
self,
|
|
434
|
+
_exc_type: type[BaseException] | None,
|
|
435
|
+
_exc_val: BaseException | None,
|
|
436
|
+
_exc_tb: TracebackType | None,
|
|
437
|
+
/,
|
|
438
|
+
) -> None:
|
|
439
|
+
"""Exit the async context manager and close."""
|
|
440
|
+
await self.aclose()
|
|
441
|
+
|
|
442
|
+
async def __call__(
|
|
443
|
+
self,
|
|
444
|
+
*,
|
|
445
|
+
method: str,
|
|
446
|
+
url: str,
|
|
447
|
+
headers: dict[str, str],
|
|
448
|
+
params: dict[str, str | int] | None,
|
|
449
|
+
json: Mapping[str, JSONValue] | None,
|
|
450
|
+
files: Mapping[str, Any] | None,
|
|
451
|
+
) -> TransportResponse:
|
|
452
|
+
"""Make an async HTTP request using ``httpx2``.
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
method: The HTTP method.
|
|
456
|
+
url: The full URL.
|
|
457
|
+
headers: Request headers.
|
|
458
|
+
params: Query parameters.
|
|
459
|
+
json: A JSON-serialisable body.
|
|
460
|
+
files: Files to send as multipart form-data.
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
A ``TransportResponse`` populated from the httpx2
|
|
464
|
+
response.
|
|
465
|
+
"""
|
|
466
|
+
response = await self._client.request(
|
|
467
|
+
method=method,
|
|
468
|
+
url=url,
|
|
469
|
+
headers=headers,
|
|
470
|
+
params=params,
|
|
471
|
+
json=json,
|
|
472
|
+
files=files,
|
|
473
|
+
)
|
|
474
|
+
return TransportResponse(
|
|
475
|
+
status_code=response.status_code,
|
|
476
|
+
headers=dict(response.headers),
|
|
477
|
+
content=response.content,
|
|
478
|
+
)
|