Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 52653e4

Browse files
author
Ahmed TAHRI
committedOct 16, 2023
⚗️ Try compatible fork Niquests to supercharge HTTPie
1 parent e52a60e commit 52653e4

38 files changed

+290
-202
lines changed
 

‎.github/workflows/tests.yml

+1-6
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ jobs:
2525
fail-fast: false
2626
matrix:
2727
os: [ubuntu-latest, macos-latest, windows-latest]
28-
python-version: [3.7, 3.8, 3.9, "3.10"]
29-
pyopenssl: [0, 1]
28+
python-version: [3.7, 3.8, 3.9, "3.10", "3.11", "3.12"]
3029
runs-on: ${{ matrix.os }}
3130
steps:
3231
- uses: actions/checkout@v3
@@ -39,12 +38,8 @@ jobs:
3938
python -m pip install --upgrade pip wheel
4039
python -m pip install --upgrade '.[dev]'
4140
python -m pytest --verbose ./httpie ./tests
42-
env:
43-
HTTPIE_TEST_WITH_PYOPENSSL: ${{ matrix.pyopenssl }}
4441
- name: Linux & Mac setup
4542
if: matrix.os != 'windows-latest'
4643
run: |
4744
make install
4845
make test
49-
env:
50-
HTTPIE_TEST_WITH_PYOPENSSL: ${{ matrix.pyopenssl }}

‎docs/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2556,7 +2556,7 @@ HTTPie has the following community channels:
25562556
25572557
Under the hood, HTTPie uses these two amazing libraries:
25582558
2559-
- [Requests](https://requests.readthedocs.io/en/latest/) — Python HTTP library for humans
2559+
- [Niquests](https://niquests.readthedocs.io/en/latest/) — Python HTTP library for humans
25602560
- [Pygments](https://pygments.org/) — Python syntax highlighter
25612561
25622562
#### HTTPie friends

‎docs/contributors/fetch.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
22
Generate the contributors database.
33
4-
FIXME: replace `requests` calls with the HTTPie API, when available.
4+
FIXME: replace `niquests` calls with the HTTPie API, when available.
55
"""
66
import json
77
import os
@@ -14,7 +14,7 @@
1414
from time import sleep
1515
from typing import Any, Dict, Optional, Set
1616

17-
import requests
17+
import niquests
1818

1919
FullNames = Set[str]
2020
GitHubLogins = Set[str]
@@ -197,10 +197,10 @@ def fetch(url: str, params: Optional[Dict[str, str]] = None) -> UserInfo:
197197
}
198198
for retry in range(1, 6):
199199
debug(f'[{retry}/5]', f'{url = }', f'{params = }')
200-
with requests.get(url, params=params, headers=headers) as req:
200+
with niquests.get(url, params=params, headers=headers) as req:
201201
try:
202202
req.raise_for_status()
203-
except requests.exceptions.HTTPError as exc:
203+
except niquests.exceptions.HTTPError as exc:
204204
if exc.response.status_code == 403:
205205
# 403 Client Error: rate limit exceeded for url: ...
206206
now = int(datetime.utcnow().timestamp())

‎httpie/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
44
"""
55

6-
__version__ = '3.2.2'
7-
__date__ = '2022-05-06'
6+
__version__ = '4.0.0'
7+
__date__ = '2023-10-11'
88
__author__ = 'Jakub Roztocil'
99
__licence__ = 'BSD'

‎httpie/adapters.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from httpie.cli.dicts import HTTPHeadersDict
2-
from requests.adapters import HTTPAdapter
2+
from niquests.adapters import HTTPAdapter
33

44

55
class HTTPieHTTPAdapter(HTTPAdapter):

‎httpie/cli/argparser.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from textwrap import dedent
88
from urllib.parse import urlsplit
99

10-
from requests.utils import get_netrc_auth
10+
from niquests.utils import get_netrc_auth
1111

1212
from .argtypes import (
1313
AuthCredentials, SSLCredentials, KeyValueArgType,

‎httpie/client.py

+24-38
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import argparse
2-
import http.client
32
import json
43
import sys
5-
from contextlib import contextmanager
64
from time import monotonic
75
from typing import Any, Dict, Callable, Iterable
86
from urllib.parse import urlparse, urlunparse
97

10-
import requests
8+
import niquests
119
# noinspection PyPackageRequirements
1210
import urllib3
1311
from urllib3.util import SKIP_HEADER, SKIPPABLE_HEADERS
@@ -44,6 +42,7 @@ def collect_messages(
4442
env: Environment,
4543
args: argparse.Namespace,
4644
request_body_read_callback: Callable[[bytes], None] = None,
45+
prepared_request_readiness: Callable[[niquests.PreparedRequest], None] = None,
4746
) -> Iterable[RequestsMessage]:
4847
httpie_session = None
4948
httpie_session_headers = None
@@ -88,7 +87,12 @@ def collect_messages(
8887
# TODO: reflect the split between request and send kwargs.
8988
dump_request(request_kwargs)
9089

91-
request = requests.Request(**request_kwargs)
90+
hooks = None
91+
92+
if prepared_request_readiness:
93+
hooks = {"pre_send": [prepared_request_readiness]}
94+
95+
request = niquests.Request(**request_kwargs, hooks=hooks)
9296
prepared_request = requests_session.prepare_request(request)
9397
transform_headers(request, prepared_request)
9498
if args.path_as_is:
@@ -110,12 +114,13 @@ def collect_messages(
110114
url=prepared_request.url,
111115
**send_kwargs_mergeable_from_env,
112116
)
113-
with max_headers(args.max_headers):
114-
response = requests_session.send(
115-
request=prepared_request,
116-
**send_kwargs_merged,
117-
**send_kwargs,
118-
)
117+
response = requests_session.send(
118+
request=prepared_request,
119+
**send_kwargs_merged,
120+
**send_kwargs,
121+
)
122+
if args.max_headers and len(response.headers) > args.max_headers:
123+
raise niquests.ConnectionError(f"got more than {args.max_headers} headers")
119124
response._httpie_headers_parsed_at = monotonic()
120125
expired_cookies += get_expired_cookies(
121126
response.headers.get('Set-Cookie', '')
@@ -124,7 +129,7 @@ def collect_messages(
124129
response_count += 1
125130
if response.next:
126131
if args.max_redirects and response_count == args.max_redirects:
127-
raise requests.TooManyRedirects
132+
raise niquests.TooManyRedirects
128133
if args.follow:
129134
prepared_request = response.next
130135
if args.all:
@@ -140,25 +145,12 @@ def collect_messages(
140145
httpie_session.save()
141146

142147

143-
# noinspection PyProtectedMember
144-
@contextmanager
145-
def max_headers(limit):
146-
# <https://github.com/httpie/cli/issues/802>
147-
# noinspection PyUnresolvedReferences
148-
orig = http.client._MAXHEADERS
149-
http.client._MAXHEADERS = limit or float('Inf')
150-
try:
151-
yield
152-
finally:
153-
http.client._MAXHEADERS = orig
154-
155-
156148
def build_requests_session(
157149
verify: bool,
158150
ssl_version: str = None,
159151
ciphers: str = None,
160-
) -> requests.Session:
161-
requests_session = requests.Session()
152+
) -> niquests.Session:
153+
requests_session = niquests.Session()
162154

163155
# Install our adapter.
164156
http_adapter = HTTPieHTTPAdapter()
@@ -186,7 +178,7 @@ def build_requests_session(
186178

187179
def dump_request(kwargs: dict):
188180
sys.stderr.write(
189-
f'\n>>> requests.request(**{repr_dict(kwargs)})\n\n')
181+
f'\n>>> niquests.request(**{repr_dict(kwargs)})\n\n')
190182

191183

192184
def finalize_headers(headers: HTTPHeadersDict) -> HTTPHeadersDict:
@@ -210,13 +202,13 @@ def finalize_headers(headers: HTTPHeadersDict) -> HTTPHeadersDict:
210202

211203

212204
def transform_headers(
213-
request: requests.Request,
214-
prepared_request: requests.PreparedRequest
205+
request: niquests.Request,
206+
prepared_request: niquests.PreparedRequest
215207
) -> None:
216208
"""Apply various transformations on top of the `prepared_requests`'s
217209
headers to change the request prepreation behavior."""
218210

219-
# Remove 'Content-Length' when it is misplaced by requests.
211+
# Remove 'Content-Length' when it is misplaced by niquests.
220212
if (
221213
prepared_request.method in IGNORE_CONTENT_LENGTH_METHODS
222214
and prepared_request.headers.get('Content-Length') == '0'
@@ -232,7 +224,7 @@ def transform_headers(
232224

233225
def apply_missing_repeated_headers(
234226
original_headers: HTTPHeadersDict,
235-
prepared_request: requests.PreparedRequest
227+
prepared_request: niquests.PreparedRequest
236228
) -> None:
237229
"""Update the given `prepared_request`'s headers with the original
238230
ones. This allows the requests to be prepared as usual, and then later
@@ -290,12 +282,6 @@ def make_send_kwargs_mergeable_from_env(args: argparse.Namespace) -> dict:
290282
if args.cert:
291283
cert = args.cert
292284
if args.cert_key:
293-
# Having a client certificate key passphrase is not supported
294-
# by requests. So we are using our own transportation structure
295-
# which is compatible with their format (a tuple of minimum two
296-
# items).
297-
#
298-
# See: https://github.com/psf/requests/issues/2519
299285
cert = HTTPieCertificate(cert, args.cert_key, args.cert_key_pass.value)
300286

301287
return {
@@ -329,7 +315,7 @@ def make_request_kwargs(
329315
request_body_read_callback=lambda chunk: chunk
330316
) -> dict:
331317
"""
332-
Translate our `args` into `requests.Request` keyword arguments.
318+
Translate our `args` into `niquests.Request` keyword arguments.
333319
334320
"""
335321
files = args.files

‎httpie/core.py

+97-8
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
import socket
66
from typing import List, Optional, Union, Callable
77

8-
import requests
8+
import niquests
99
from pygments import __version__ as pygments_version
10-
from requests import __version__ as requests_version
10+
from niquests import __version__ as requests_version
1111

1212
from . import __version__ as httpie_version
1313
from .cli.constants import OUT_REQ_BODY
@@ -112,16 +112,16 @@ def handle_generic_error(e, annotation=None):
112112
if include_traceback:
113113
raise
114114
exit_status = ExitStatus.ERROR
115-
except requests.Timeout:
115+
except niquests.Timeout:
116116
exit_status = ExitStatus.ERROR_TIMEOUT
117117
env.log_error(f'Request timed out ({parsed_args.timeout}s).')
118-
except requests.TooManyRedirects:
118+
except niquests.TooManyRedirects:
119119
exit_status = ExitStatus.ERROR_TOO_MANY_REDIRECTS
120120
env.log_error(
121121
f'Too many redirects'
122122
f' (--max-redirects={parsed_args.max_redirects}).'
123123
)
124-
except requests.exceptions.ConnectionError as exc:
124+
except niquests.exceptions.ConnectionError as exc:
125125
annotation = None
126126
original_exc = unwrap_context(exc)
127127
if isinstance(original_exc, socket.gaierror):
@@ -175,8 +175,8 @@ def program(args: argparse.Namespace, env: Environment) -> ExitStatus:
175175
# TODO: Refactor and drastically simplify, especially so that the separator logic is elsewhere.
176176
exit_status = ExitStatus.SUCCESS
177177
downloader = None
178-
initial_request: Optional[requests.PreparedRequest] = None
179-
final_response: Optional[requests.Response] = None
178+
initial_request: Optional[niquests.PreparedRequest] = None
179+
final_response: Optional[niquests.Response] = None
180180
processing_options = ProcessingOptions.from_raw_args(args)
181181

182182
def separate():
@@ -204,8 +204,94 @@ def request_body_read_callback(chunk: bytes):
204204
args.follow = True # --download implies --follow.
205205
downloader = Downloader(env, output_file=args.output_file, resume=args.download_resume)
206206
downloader.pre_request(args.headers)
207+
208+
def prepared_request_readiness(pr):
209+
nonlocal output_options, do_write_body, processing_options
210+
211+
if initial_request == pr:
212+
if args.debug and pr.conn_info and pr.conn_info.destination_address:
213+
sys.stderr.write(
214+
f"""\n>>> Connected to {pr.conn_info.destination_address[0]} port {pr.conn_info.destination_address[1]}\n"""
215+
)
216+
217+
if args.debug and pr.conn_info:
218+
if pr.conn_info.cipher:
219+
sys.stderr.write(
220+
f"""\n>>> Connection secured using {pr.conn_info.tls_version.name.replace('_', '.')} / {pr.conn_info.cipher}\n\n"""
221+
)
222+
223+
if pr.conn_info.certificate_dict:
224+
sys.stderr.write(">>> Server certificate:\n")
225+
226+
if "subject" in pr.conn_info.certificate_dict:
227+
sys.stderr.write(
228+
">>> subject: "
229+
)
230+
231+
for entry in pr.conn_info.certificate_dict['subject']:
232+
if len(entry) == 2:
233+
rdns, value = entry
234+
elif len(entry) == 1:
235+
rdns, value = entry[0]
236+
else:
237+
continue
238+
239+
sys.stderr.write(f'{rdns}="{value}"; ')
240+
241+
sys.stderr.write("\n")
242+
243+
sys.stderr.write(f">>> start date: {pr.conn_info.certificate_dict['notBefore']}\n")
244+
sys.stderr.write(f">>> expire date: {pr.conn_info.certificate_dict['notAfter']}\n")
245+
246+
if "subjectAltName" in pr.conn_info.certificate_dict:
247+
sys.stderr.write(
248+
">>> subjectAltName: "
249+
)
250+
251+
for entry in pr.conn_info.certificate_dict['subjectAltName']:
252+
if len(entry) == 2:
253+
rdns, value = entry
254+
sys.stderr.write(f'{rdns}="{value}"; ')
255+
256+
sys.stderr.write("\n")
257+
258+
if "issuer" in pr.conn_info.certificate_dict:
259+
sys.stderr.write(
260+
">>> issuer: "
261+
)
262+
263+
for entry in pr.conn_info.certificate_dict['issuer']:
264+
if len(entry) == 2:
265+
rdns, value = entry
266+
elif len(entry) == 1:
267+
rdns, value = entry[0]
268+
else:
269+
continue
270+
271+
sys.stderr.write(f'{rdns}="{value}"; ')
272+
273+
sys.stderr.write("\n\n")
274+
275+
if pr.ocsp_verified is None:
276+
sys.stderr.write(">>> Revocation status: Unverified\n\n")
277+
elif pr.ocsp_verified:
278+
sys.stderr.write(">>> Revocation status: Good\n\n")
279+
else:
280+
sys.stderr.write(">>> Revocation status: Error\n\n")
281+
else:
282+
sys.stderr.write("\n")
283+
284+
write_message(
285+
requests_message=pr,
286+
env=env,
287+
output_options=output_options._replace(
288+
body=do_write_body
289+
),
290+
processing_options=processing_options
291+
)
292+
207293
messages = collect_messages(env, args=args,
208-
request_body_read_callback=request_body_read_callback)
294+
request_body_read_callback=request_body_read_callback, prepared_request_readiness=prepared_request_readiness)
209295
force_separator = False
210296
prev_with_body = False
211297

@@ -225,6 +311,9 @@ def request_body_read_callback(chunk: bytes):
225311
is_streamed_upload = not isinstance(message.body, (str, bytes))
226312
do_write_body = not is_streamed_upload
227313
force_separator = is_streamed_upload and env.stdout_isatty
314+
if message.conn_info is None and not args.offline:
315+
prev_with_body = output_options.body
316+
continue
228317
else:
229318
final_response = message
230319
if args.check_status or downloader:

‎httpie/downloads.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from typing import IO, Optional, Tuple
1111
from urllib.parse import urlsplit
1212

13-
import requests
13+
import niquests
1414

1515
from .models import HTTPResponse, OutputOptions
1616
from .output.streams import RawStream
@@ -202,7 +202,7 @@ def pre_request(self, request_headers: dict):
202202
def start(
203203
self,
204204
initial_url: str,
205-
final_response: requests.Response
205+
final_response: niquests.Response
206206
) -> Tuple[RawStream, IO]:
207207
"""
208208
Initiate and return a stream for `response` body with progress
@@ -288,7 +288,7 @@ def chunk_downloaded(self, chunk: bytes):
288288
@staticmethod
289289
def _get_output_file_from_response(
290290
initial_url: str,
291-
final_response: requests.Response,
291+
final_response: niquests.Response,
292292
) -> IO:
293293
# Output file not specified. Pick a name that doesn't exist yet.
294294
filename = None

‎httpie/internal/update_warnings.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pathlib import Path
55
from typing import Any, Optional, Callable
66

7-
import requests
7+
import niquests
88

99
import httpie
1010
from httpie.context import Environment, LogLevel
@@ -41,7 +41,7 @@ def _fetch_updates(env: Environment) -> str:
4141
file = env.config.version_info_file
4242
data = _read_data_error_free(file)
4343

44-
response = requests.get(PACKAGE_INDEX_LINK, verify=False)
44+
response = niquests.get(PACKAGE_INDEX_LINK, verify=False)
4545
response.raise_for_status()
4646

4747
data.setdefault('last_warned_date', None)

‎httpie/models.py

+14-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from time import monotonic
22

3-
import requests
3+
import niquests
44
from urllib3.util import SKIP_HEADER, SKIPPABLE_HEADERS
5+
from kiss_headers.utils import prettify_header_name
56

67
from enum import Enum, auto
78
from typing import Iterable, Union, NamedTuple
@@ -59,7 +60,7 @@ def content_type(self) -> str:
5960

6061

6162
class HTTPResponse(HTTPMessage):
62-
"""A :class:`requests.models.Response` wrapper."""
63+
"""A :class:`niquests.models.Response` wrapper."""
6364

6465
def iter_body(self, chunk_size=1):
6566
return self._orig.iter_content(chunk_size=chunk_size)
@@ -73,7 +74,7 @@ def headers(self):
7374
status_line = f'HTTP/{self.version} {original.status_code} {original.reason}'
7475
headers = [status_line]
7576
headers.extend(
76-
': '.join(header)
77+
': '.join([prettify_header_name(header[0]), header[1]])
7778
for header in original.headers.items()
7879
if header[0] != 'Set-Cookie'
7980
)
@@ -112,7 +113,8 @@ def version(self) -> str:
112113
9: '0.9',
113114
10: '1.0',
114115
11: '1.1',
115-
20: '2.0',
116+
20: '2',
117+
30: '3',
116118
}
117119
fallback = 11
118120
version = None
@@ -128,7 +130,7 @@ def version(self) -> str:
128130

129131

130132
class HTTPRequest(HTTPMessage):
131-
"""A :class:`requests.models.Request` wrapper."""
133+
"""A :class:`niquests.models.Request` wrapper."""
132134

133135
def iter_body(self, chunk_size):
134136
yield self.body
@@ -140,10 +142,11 @@ def iter_lines(self, chunk_size):
140142
def headers(self):
141143
url = urlsplit(self._orig.url)
142144

143-
request_line = '{method} {path}{query} HTTP/1.1'.format(
145+
request_line = '{method} {path}{query} {http_version}'.format(
144146
method=self._orig.method,
145147
path=url.path or '/',
146-
query=f'?{url.query}' if url.query else ''
148+
query=f'?{url.query}' if url.query else '',
149+
http_version=self._orig.conn_info.http_version.value.replace(".0", "") if self._orig.conn_info and self._orig.conn_info.http_version else "HTTP/1.1"
147150
)
148151

149152
headers = self._orig.headers.copy()
@@ -158,6 +161,7 @@ def headers(self):
158161

159162
headers.insert(0, request_line)
160163
headers = '\r\n'.join(headers).strip()
164+
161165
return headers
162166

163167
@property
@@ -169,7 +173,7 @@ def body(self):
169173
return body or b''
170174

171175

172-
RequestsMessage = Union[requests.PreparedRequest, requests.Response]
176+
RequestsMessage = Union[niquests.PreparedRequest, niquests.Response]
173177

174178

175179
class RequestsMessageKind(Enum):
@@ -178,9 +182,9 @@ class RequestsMessageKind(Enum):
178182

179183

180184
def infer_requests_message_kind(message: RequestsMessage) -> RequestsMessageKind:
181-
if isinstance(message, requests.PreparedRequest):
185+
if isinstance(message, niquests.PreparedRequest):
182186
return RequestsMessageKind.REQUEST
183-
elif isinstance(message, requests.Response):
187+
elif isinstance(message, niquests.Response):
184188
return RequestsMessageKind.RESPONSE
185189
else:
186190
raise TypeError(f"Unexpected message type: {type(message).__name__}")

‎httpie/output/writer.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import errno
2-
import requests
2+
import niquests
33
from typing import Any, Dict, IO, Optional, TextIO, Tuple, Type, Union
44

55
from ..cli.dicts import HTTPHeadersDict
@@ -105,7 +105,7 @@ def write_raw_data(
105105
headers: Optional[HTTPHeadersDict] = None,
106106
stream_kwargs: Optional[Dict[str, Any]] = None
107107
):
108-
msg = requests.PreparedRequest()
108+
msg = niquests.PreparedRequest()
109109
msg.is_body_upload_chunk = True
110110
msg.body = data
111111
msg.headers = headers or HTTPHeadersDict()

‎httpie/plugins/base.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def get_auth(self, username: str = None, password: str = None):
6363
Use `self.raw_auth` to access the raw value passed through
6464
`--auth, -a`.
6565
66-
Return a ``requests.auth.AuthBase`` subclass instance.
66+
Return a ``niquests.auth.AuthBase`` subclass instance.
6767
6868
"""
6969
raise NotImplementedError()
@@ -73,7 +73,7 @@ class TransportPlugin(BasePlugin):
7373
"""
7474
Requests transport adapter docs:
7575
76-
<https://requests.readthedocs.io/en/latest/user/advanced/#transport-adapters>
76+
<https://niquests.readthedocs.io/en/latest/user/advanced/#transport-adapters>
7777
7878
See httpie-unixsocket for an example transport plugin:
7979
@@ -86,7 +86,7 @@ class TransportPlugin(BasePlugin):
8686

8787
def get_adapter(self):
8888
"""
89-
Return a ``requests.adapters.BaseAdapter`` subclass instance to be
89+
Return a ``niquests.adapters.BaseAdapter`` subclass instance to be
9090
mounted to ``self.prefix``.
9191
9292
"""

‎httpie/plugins/builtin.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from base64 import b64encode
22

3-
import requests.auth
3+
import niquests.auth
44

55
from .base import AuthPlugin
66

@@ -10,12 +10,12 @@ class BuiltinAuthPlugin(AuthPlugin):
1010
package_name = '(builtin)'
1111

1212

13-
class HTTPBasicAuth(requests.auth.HTTPBasicAuth):
13+
class HTTPBasicAuth(niquests.auth.HTTPBasicAuth):
1414

1515
def __call__(
1616
self,
17-
request: requests.PreparedRequest
18-
) -> requests.PreparedRequest:
17+
request: niquests.PreparedRequest
18+
) -> niquests.PreparedRequest:
1919
"""
2020
Override username/password serialization to allow unicode.
2121
@@ -34,12 +34,12 @@ def make_header(username: str, password: str) -> str:
3434
return f'Basic {token}'
3535

3636

37-
class HTTPBearerAuth(requests.auth.AuthBase):
37+
class HTTPBearerAuth(niquests.auth.AuthBase):
3838

3939
def __init__(self, token: str) -> None:
4040
self.token = token
4141

42-
def __call__(self, request: requests.PreparedRequest) -> requests.PreparedRequest:
42+
def __call__(self, request: niquests.PreparedRequest) -> niquests.PreparedRequest:
4343
request.headers['Authorization'] = f'Bearer {self.token}'
4444
return request
4545

@@ -64,8 +64,8 @@ def get_auth(
6464
self,
6565
username: str,
6666
password: str
67-
) -> requests.auth.HTTPDigestAuth:
68-
return requests.auth.HTTPDigestAuth(username, password)
67+
) -> niquests.auth.HTTPDigestAuth:
68+
return niquests.auth.HTTPDigestAuth(username, password)
6969

7070

7171
class BearerAuthPlugin(BuiltinAuthPlugin):
@@ -75,5 +75,5 @@ class BearerAuthPlugin(BuiltinAuthPlugin):
7575
auth_parse = False
7676

7777
# noinspection PyMethodOverriding
78-
def get_auth(self, **kwargs) -> requests.auth.HTTPDigestAuth:
78+
def get_auth(self, **kwargs) -> niquests.auth.HTTPDigestAuth:
7979
return HTTPBearerAuth(self.raw_auth)

‎httpie/sessions.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
from pathlib import Path
1111
from typing import Any, Dict, List, Optional, Union
1212

13-
from requests.auth import AuthBase
14-
from requests.cookies import RequestsCookieJar, remove_cookie_by_name
13+
from niquests.auth import AuthBase
14+
from niquests.cookies import RequestsCookieJar, remove_cookie_by_name
1515

1616
from .context import Environment, LogLevel
1717
from .cookies import HTTPieCookiePolicy

‎httpie/ssl_.py

+53-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import ssl
2-
from typing import NamedTuple, Optional
2+
from typing import NamedTuple, Optional, Tuple, MutableMapping
3+
import json
4+
import os.path
5+
from os import makedirs
36

7+
from httpie.config import DEFAULT_CONFIG_DIR
48
from httpie.adapters import HTTPAdapter
59
# noinspection PyPackageRequirements
610
from urllib3.util.ssl_ import (
@@ -10,10 +14,6 @@
1014

1115

1216
SSL_VERSION_ARG_MAPPING = {
13-
'ssl2.3': 'PROTOCOL_SSLv23',
14-
'ssl3': 'PROTOCOL_SSLv3',
15-
'tls1': 'PROTOCOL_TLSv1',
16-
'tls1.1': 'PROTOCOL_TLSv1_1',
1717
'tls1.2': 'PROTOCOL_TLSv1_2',
1818
'tls1.3': 'PROTOCOL_TLSv1_3',
1919
}
@@ -24,6 +24,50 @@
2424
}
2525

2626

27+
class QuicCapabilityCache(
28+
MutableMapping[Tuple[str, int], Optional[Tuple[str, int]]]
29+
):
30+
31+
def __init__(self):
32+
self._cache = {}
33+
if not os.path.exists(DEFAULT_CONFIG_DIR):
34+
makedirs(DEFAULT_CONFIG_DIR, exist_ok=True)
35+
if os.path.exists(os.path.join(DEFAULT_CONFIG_DIR, "quic.json")):
36+
with open(os.path.join(DEFAULT_CONFIG_DIR, "quic.json"), "r") as fp:
37+
self._cache = json.load(fp)
38+
39+
def save(self):
40+
with open(os.path.join(DEFAULT_CONFIG_DIR, "quic.json"), "w+") as fp:
41+
json.dump(self._cache, fp)
42+
43+
def __contains__(self, item: Tuple[str, int]):
44+
return f"QUIC_{item[0]}_{item[1]}" in self._cache
45+
46+
def __setitem__(self, key: Tuple[str, int], value: Optional[Tuple[str, int]]):
47+
self._cache[f"QUIC_{key[0]}_{key[1]}"] = f"{value[0]}:{value[1]}"
48+
self.save()
49+
50+
def __getitem__(self, item: Tuple[str, int]):
51+
key: str = f"QUIC_{item[0]}_{item[1]}"
52+
if key in self._cache:
53+
host, port = self._cache[key].split(":")
54+
return host, int(port)
55+
56+
return None
57+
58+
def __delitem__(self, key: Tuple[str, int]):
59+
key: str = f"QUIC_{key[0]}_{key[1]}"
60+
if key in self._cache:
61+
del self._cache[key]
62+
self.save()
63+
64+
def __len__(self):
65+
return len(self._cache)
66+
67+
def __iter__(self):
68+
yield from self._cache.items()
69+
70+
2771
class HTTPieCertificate(NamedTuple):
2872
cert_file: Optional[str] = None
2973
key_file: Optional[str] = None
@@ -32,7 +76,9 @@ class HTTPieCertificate(NamedTuple):
3276
def to_raw_cert(self):
3377
"""Synthesize a requests-compatible (2-item tuple of cert and key file)
3478
object from HTTPie's internal representation of a certificate."""
35-
return (self.cert_file, self.key_file)
79+
if self.key_password:
80+
return self.cert_file, self.key_file, self.key_password
81+
return self.cert_file, self.key_file
3682

3783

3884
class HTTPieHTTPSAdapter(HTTPAdapter):
@@ -48,6 +94,7 @@ def __init__(
4894
ssl_version=ssl_version,
4995
ciphers=ciphers,
5096
)
97+
kwargs.setdefault("quic_cache_layer", QuicCapabilityCache())
5198
super().__init__(**kwargs)
5299

53100
def init_poolmanager(self, *args, **kwargs):
@@ -60,7 +107,6 @@ def proxy_manager_for(self, *args, **kwargs):
60107

61108
def cert_verify(self, conn, url, verify, cert):
62109
if isinstance(cert, HTTPieCertificate):
63-
conn.key_password = cert.key_password
64110
cert = cert.to_raw_cert()
65111

66112
return super().cert_verify(conn, url, verify, cert)

‎httpie/uploads.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
from typing import Any, Callable, IO, Iterable, Optional, Tuple, Union, TYPE_CHECKING
77
from urllib.parse import urlencode
88

9-
import requests
10-
from requests.utils import super_len
9+
import niquests
10+
from niquests.utils import super_len
1111

1212
if TYPE_CHECKING:
1313
from requests_toolbelt import MultipartEncoder
@@ -250,7 +250,7 @@ def get_multipart_data_and_content_type(
250250

251251

252252
def compress_request(
253-
request: requests.PreparedRequest,
253+
request: niquests.PreparedRequest,
254254
always: bool,
255255
):
256256
deflater = zlib.compressobj()

‎httpie/utils.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from urllib.parse import urlsplit
1717
from typing import Any, List, Optional, Tuple, Generator, Callable, Iterable, IO, TypeVar
1818

19-
import requests.auth
19+
import niquests.auth
2020

2121
RE_COOKIE_SPLIT = re.compile(r', (?=[^ ;]+=)')
2222
Item = Tuple[str, Any]
@@ -121,7 +121,7 @@ def humanize_bytes(n, precision=2):
121121
return f'{n / factor:.{precision}f} {suffix}'
122122

123123

124-
class ExplicitNullAuth(requests.auth.AuthBase):
124+
class ExplicitNullAuth(niquests.auth.AuthBase):
125125
"""Forces requests to ignore the ``.netrc``.
126126
<https://github.com/psf/requests/issues/2773#issuecomment-174312831>
127127
"""
@@ -201,7 +201,7 @@ def _max_age_to_expires(cookies, now):
201201

202202

203203
def parse_content_type_header(header):
204-
"""Borrowed from requests."""
204+
"""Borrowed from niquests."""
205205
tokens = header.split(';')
206206
content_type, params = tokens[0].strip(), tokens[1:]
207207
params_dict = {}

‎pytest.ini

-7
This file was deleted.

‎setup.cfg

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ testpaths = httpie tests
1111
norecursedirs = tests/fixtures
1212
addopts = --tb=native --doctest-modules --verbose
1313
xfail_strict = True
14-
14+
markers =
15+
# If you want to run tests without a full HTTPie installation
16+
# we advise you to disable the markers below, e.g:
17+
# pytest -m 'not requires_installation and not requires_external_processes'
18+
requires_installation
19+
requires_external_processes
1520

1621
[flake8]
1722
# <https://flake8.pycqa.org/en/latest/user/error-codes.html>

‎setup.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
'flake8-deprecated',
2424
'flake8-mutable',
2525
'flake8-tuple',
26-
'pyopenssl',
2726
'pytest-cov',
2827
'pyyaml',
2928
'twine',
@@ -34,7 +33,7 @@
3433
'pip',
3534
'charset_normalizer>=2.0.0',
3635
'defusedxml>=0.6.0',
37-
'requests[socks]>=2.22.0',
36+
'niquests[socks]>=3.0.0',
3837
'Pygments>=2.5.2',
3938
'requests-toolbelt>=0.9.1',
4039
'multidict>=4.7.0',

‎tests/conftest.py

+11-17
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN,
88
HTTPBIN_WITH_CHUNKED_SUPPORT,
99
REMOTE_HTTPBIN_DOMAIN,
10-
IS_PYOPENSSL,
1110
mock_env
1211
)
1312
from .utils.plugins_cli import ( # noqa
@@ -20,6 +19,17 @@
2019
)
2120
from .utils.http_server import http_server, localhost_http_server # noqa
2221

22+
from sys import modules
23+
24+
import niquests
25+
import urllib3
26+
27+
# the mock utility 'response' only works with 'requests'
28+
modules["requests"] = niquests
29+
modules["requests.adapters"] = niquests.adapters
30+
modules["requests.exceptions"] = niquests.exceptions
31+
modules["requests.packages.urllib3"] = urllib3
32+
2333

2434
@pytest.fixture(scope='function', autouse=True)
2535
def httpbin_add_ca_bundle(monkeypatch):
@@ -73,19 +83,3 @@ def remote_httpbin(_remote_httpbin_available):
7383
if _remote_httpbin_available:
7484
return 'http://' + REMOTE_HTTPBIN_DOMAIN
7585
pytest.skip(f'{REMOTE_HTTPBIN_DOMAIN} not resolvable')
76-
77-
78-
@pytest.fixture(autouse=True, scope='session')
79-
def pyopenssl_inject():
80-
"""
81-
Injects `pyOpenSSL` module to make sure `requests` will use it.
82-
<https://github.com/psf/requests/pull/5443#issuecomment-645740394>
83-
"""
84-
if IS_PYOPENSSL:
85-
try:
86-
import urllib3.contrib.pyopenssl
87-
urllib3.contrib.pyopenssl.inject_into_urllib3()
88-
except ModuleNotFoundError:
89-
pytest.fail('Missing "pyopenssl" module.')
90-
91-
yield

‎tests/test_auth.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ def test_missing_auth(httpbin):
9393

9494
def test_netrc(httpbin_both):
9595
# This one gets handled by requests (no --auth, --auth-type present),
96-
# that’s why we patch inside `requests.sessions`.
97-
with mock.patch('requests.sessions.get_netrc_auth') as get_netrc_auth:
96+
# that’s why we patch inside `niquests.sessions`.
97+
with mock.patch('niquests.sessions.get_netrc_auth') as get_netrc_auth:
9898
get_netrc_auth.return_value = ('httpie', 'password')
9999
r = http(httpbin_both + '/basic-auth/httpie/password')
100100
assert get_netrc_auth.call_count == 1
@@ -106,7 +106,7 @@ def test_ignore_netrc(httpbin_both):
106106
get_netrc_auth.return_value = ('httpie', 'password')
107107
r = http('--ignore-netrc', httpbin_both + '/basic-auth/httpie/password')
108108
assert get_netrc_auth.call_count == 0
109-
assert 'HTTP/1.1 401 UNAUTHORIZED' in r
109+
assert 'HTTP/1.1 401 Unauthorized' in r
110110

111111

112112
def test_ignore_netrc_together_with_auth():

‎tests/test_binary.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Tests for dealing with binary request and response data."""
2-
import requests
2+
import niquests
33

44
from .fixtures import BIN_FILE_PATH, BIN_FILE_CONTENT, BIN_FILE_PATH_ARG
55
from httpie.output.streams import BINARY_SUPPRESSED_NOTICE
@@ -46,5 +46,5 @@ def test_binary_included_and_correct_when_suitable(self, httpbin):
4646
env = MockEnvironment(stdin_isatty=True, stdout_isatty=False)
4747
url = httpbin + '/bytes/1024?seed=1'
4848
r = http('GET', url, env=env)
49-
expected = requests.get(url).content
49+
expected = niquests.get(url).content
5050
assert r == expected

‎tests/test_cli.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import argparse
33

44
import pytest
5-
from requests.exceptions import InvalidSchema
5+
from niquests.exceptions import InvalidSchema
66

77
import httpie.cli.argparser
88
from httpie.cli import constants

‎tests/test_cookie.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class MockServerRequestHandler(BaseHTTPRequestHandler):
2828
""""HTTP request handler."""
2929

3030
def do_GET(self):
31-
"""Handle GET requests."""
31+
"""Handle GET niquests."""
3232
# Craft multiple cookies
3333
cookie = SimpleCookie()
3434
cookie['hello'] = 'world'

‎tests/test_downloads.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import os
22
import tempfile
33
import time
4-
import requests
4+
import niquests
55
from unittest import mock
66
from urllib.request import urlopen
77

88
import pytest
9-
from requests.structures import CaseInsensitiveDict
9+
from niquests.structures import CaseInsensitiveDict
1010

1111
from httpie.downloads import (
1212
parse_content_range, filename_from_content_disposition, filename_from_url,
@@ -15,7 +15,7 @@
1515
from .utils import http, MockEnvironment
1616

1717

18-
class Response(requests.Response):
18+
class Response(niquests.Response):
1919
# noinspection PyDefaultArgument
2020
def __init__(self, url, headers={}, status_code=200):
2121
self.url = url

‎tests/test_errors.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from unittest import mock
44
from pytest import raises
55
from requests import Request
6-
from requests.exceptions import ConnectionError
6+
from niquests.exceptions import ConnectionError
77

88
from httpie.status import ExitStatus
99
from .utils import HTTP_OK, http

‎tests/test_exit_status.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def test_ok_response_exits_0(httpbin):
2626

2727
def test_error_response_exits_0_without_check_status(httpbin):
2828
r = http('GET', httpbin.url + '/status/500')
29-
assert '500 INTERNAL SERVER ERROR' in r
29+
assert '500 Internal Server Error' in r
3030
assert r.exit_status == ExitStatus.SUCCESS
3131
assert not r.stderr
3232

@@ -44,7 +44,7 @@ def test_3xx_check_status_exits_3_and_stderr_when_stdout_redirected(
4444
r = http('--check-status', '--headers',
4545
'GET', httpbin.url + '/status/301',
4646
env=env, tolerate_error_exit_status=True)
47-
assert '301 MOVED PERMANENTLY' in r
47+
assert '301 Moved Permanently' in r
4848
assert r.exit_status == ExitStatus.ERROR_HTTP_3XX
4949
assert '301 moved permanently' in r.stderr.lower()
5050

@@ -61,7 +61,7 @@ def test_3xx_check_status_redirects_allowed_exits_0(httpbin):
6161
def test_4xx_check_status_exits_4(httpbin):
6262
r = http('--check-status', 'GET', httpbin.url + '/status/401',
6363
tolerate_error_exit_status=True)
64-
assert '401 UNAUTHORIZED' in r
64+
assert '401 Unauthorized' in r
6565
assert r.exit_status == ExitStatus.ERROR_HTTP_4XX
6666
# Also stderr should be empty since stdout isn't redirected.
6767
assert not r.stderr
@@ -70,5 +70,5 @@ def test_4xx_check_status_exits_4(httpbin):
7070
def test_5xx_check_status_exits_5(httpbin):
7171
r = http('--check-status', 'GET', httpbin.url + '/status/500',
7272
tolerate_error_exit_status=True)
73-
assert '500 INTERNAL SERVER ERROR' in r
73+
assert '500 Internal Server Error' in r
7474
assert r.exit_status == ExitStatus.ERROR_HTTP_5XX

‎tests/test_json.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -338,13 +338,14 @@ def test_complex_json_arguments_with_non_json(httpbin, request_type, value):
338338
[
339339
r'foo\[key\]:=1',
340340
r'bar\[1\]:=2',
341-
r'baz\[\]:3',
341+
r'baz\[\]:=3',
342342
r'quux[key\[escape\]]:=4',
343343
r'quux[key 2][\\][\\\\][\\\[\]\\\]\\\[\n\\]:=5',
344344
],
345345
{
346346
'foo[key]': 1,
347347
'bar[1]': 2,
348+
'baz[]': 3,
348349
'quux': {
349350
'key[escape]': 4,
350351
'key 2': {'\\': {'\\\\': {'\\[]\\]\\[\\n\\': 5}}},

‎tests/test_output.py

+14-10
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from urllib.request import urlopen
1010

1111
import pytest
12-
import requests
12+
import niquests
1313
import responses
1414

1515
from httpie.cli.argtypes import (
@@ -97,18 +97,22 @@ def test_quiet_quiet_with_check_status_non_zero_pipe(self, httpbin):
9797
(['-q'], 1),
9898
(['-qq'], 0),
9999
])
100-
# Might fail on Windows due to interference from other warnings.
101-
@pytest.mark.xfail
102100
def test_quiet_on_python_warnings(self, test_patch, httpbin, flags, expected_warnings):
103101
def warn_and_run(*args, **kwargs):
104102
warnings.warn('warning!!')
105103
return ExitStatus.SUCCESS
106104

107105
test_patch.side_effect = warn_and_run
108-
with pytest.warns(None) as record:
109-
http(*flags, httpbin + '/get')
110106

111-
assert len(record) == expected_warnings
107+
if expected_warnings == 0:
108+
with warnings.catch_warnings():
109+
warnings.simplefilter("error")
110+
http(*flags, httpbin + '/get')
111+
else:
112+
with pytest.warns(Warning) as record:
113+
http(*flags, httpbin + '/get')
114+
115+
assert len(record) >= expected_warnings
112116

113117
def test_double_quiet_on_error(self, httpbin):
114118
r = http(
@@ -160,7 +164,7 @@ def test_quiet_with_output_redirection(self, tmp_path, httpbin, quiet_flags, wit
160164
output_path = Path('output.txt')
161165
env = MockEnvironment()
162166
orig_cwd = os.getcwd()
163-
output = requests.get(url).text
167+
output = niquests.get(url).text
164168
extra_args = ['--download'] if with_download else []
165169
os.chdir(tmp_path)
166170
try:
@@ -214,7 +218,7 @@ def test_verbose_json(self, httpbin):
214218
def test_verbose_implies_all(self, httpbin):
215219
r = http('--verbose', '--follow', httpbin + '/redirect/1')
216220
assert 'GET /redirect/1 HTTP/1.1' in r
217-
assert 'HTTP/1.1 302 FOUND' in r
221+
assert 'HTTP/1.1 302 Found' in r
218222
assert 'GET /get HTTP/1.1' in r
219223
assert HTTP_OK in r
220224

@@ -273,7 +277,7 @@ def test_ensure_meta_is_colored(httpbin, style):
273277
' ',
274278
' OK',
275279
' OK ',
276-
' CUSTOM ',
280+
# ' CUSTOM ', Unsupported.
277281
])
278282
def test_ensure_status_code_is_shown_on_all_themes(http_server, style, msg):
279283
env = MockEnvironment(colors=256)
@@ -282,7 +286,7 @@ def test_ensure_status_code_is_shown_on_all_themes(http_server, style, msg):
282286
'--raw', msg, env=env)
283287

284288
# Trailing space is stripped away.
285-
assert 'HTTP/1.0 200' + msg.rstrip() in strip_colors(r)
289+
assert 'HTTP/1.1 200' + msg.rstrip() in strip_colors(r)
286290

287291

288292
class TestPrettyOptions:

‎tests/test_redirects.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
def test_follow_all_redirects_shown(httpbin):
1616
r = http('--follow', '--all', httpbin.url + '/redirect/2')
1717
assert r.count('HTTP/1.1') == 3
18-
assert r.count('HTTP/1.1 302 FOUND', 2)
18+
assert r.count('HTTP/1.1 302 Found', 2)
1919
assert HTTP_OK in r
2020

2121

‎tests/test_ssl.py

+6-31
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,19 @@
22

33
import pytest
44
import pytest_httpbin.certs
5-
import requests.exceptions
5+
import niquests.exceptions
66
import urllib3
77

88
from unittest import mock
99

1010
from httpie.ssl_ import AVAILABLE_SSL_VERSION_ARG_MAPPING, DEFAULT_SSL_CIPHERS_STRING
1111
from httpie.status import ExitStatus
1212

13-
from .utils import HTTP_OK, TESTS_ROOT, IS_PYOPENSSL, http
13+
from .utils import HTTP_OK, TESTS_ROOT, http
1414

15-
16-
try:
17-
# Handle OpenSSL errors, if installed.
18-
# See <https://github.com/httpie/cli/issues/729>
19-
# noinspection PyUnresolvedReferences
20-
import OpenSSL.SSL
21-
ssl_errors = (
22-
requests.exceptions.SSLError,
23-
OpenSSL.SSL.Error,
24-
ValueError, # TODO: Remove with OSS-65
25-
)
26-
except ImportError:
27-
ssl_errors = (
28-
requests.exceptions.SSLError,
29-
)
15+
ssl_errors = (
16+
niquests.exceptions.SSLError,
17+
)
3018

3119
CERTS_ROOT = TESTS_ROOT / 'client_certs'
3220
CLIENT_CERT = str(CERTS_ROOT / 'client.crt')
@@ -59,10 +47,7 @@ def test_ssl_version(httpbin_secure, ssl_version):
5947
)
6048
assert HTTP_OK in r
6149
except ssl_errors as e:
62-
if ssl_version == 'ssl3':
63-
# pytest-httpbin doesn't support ssl3
64-
pass
65-
elif e.__context__ is not None: # Check if root cause was an unsupported TLS version
50+
if e.__context__ is not None: # Check if root cause was an unsupported TLS version
6651
root = e.__context__
6752
while root.__context__ is not None:
6853
root = root.__context__
@@ -151,7 +136,6 @@ def test_ciphers(httpbin_secure):
151136
assert HTTP_OK in r
152137

153138

154-
@pytest.mark.skipif(IS_PYOPENSSL, reason='pyOpenSSL uses a different message format.')
155139
def test_ciphers_none_can_be_selected(httpbin_secure):
156140
r = http(
157141
httpbin_secure.url + '/get',
@@ -168,15 +152,6 @@ def test_ciphers_none_can_be_selected(httpbin_secure):
168152
assert 'cipher' in r.stderr
169153

170154

171-
def test_pyopenssl_presence():
172-
if not IS_PYOPENSSL:
173-
assert not urllib3.util.ssl_.IS_PYOPENSSL
174-
assert not urllib3.util.IS_PYOPENSSL
175-
else:
176-
assert urllib3.util.ssl_.IS_PYOPENSSL
177-
assert urllib3.util.IS_PYOPENSSL
178-
179-
180155
@mock.patch('httpie.cli.argtypes.SSLCredentials._prompt_password',
181156
new=lambda self, prompt: PWD_CLIENT_PASS)
182157
def test_password_protected_cert_prompt(httpbin_secure):

‎tests/test_transport_plugin.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
from io import BytesIO
22

3-
from requests.adapters import BaseAdapter
4-
from requests.models import Response
5-
from requests.utils import get_encoding_from_headers
3+
from niquests.adapters import BaseAdapter
4+
from niquests.models import Response
5+
from niquests.utils import get_encoding_from_headers
66

77
from httpie.plugins import TransportPlugin
88
from httpie.plugins.registry import plugin_manager

‎tests/test_update_warnings.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ def fetch_update_mock(mocker):
213213

214214
@pytest.fixture
215215
def static_fetch_data(mocker):
216-
mock_get = mocker.patch('requests.get')
216+
mock_get = mocker.patch('niquests.get')
217217
mock_get.return_value.status_code = 200
218218
mock_get.return_value.json.return_value = {
219219
BUILD_CHANNEL: HIGHEST_VERSION,

‎tests/test_uploads.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -147,14 +147,14 @@ def test_stdin_read_warning(httpbin):
147147
with stdin_processes(httpbin) as (process_1, process_2):
148148
# Wait before sending any data
149149
time.sleep(1)
150-
process_1.communicate(timeout=0.1, input=b"bleh\n")
150+
process_1.communicate(timeout=0.2, input=b"bleh\n")
151151

152152
try:
153153
_, errs = process_2.communicate(timeout=MAX_RESPONSE_WAIT_TIME)
154154
except subprocess.TimeoutExpired:
155155
errs = b''
156156

157-
assert b'> warning: no stdin data read in 0.1s' in errs
157+
assert b'> warning: no stdin data read in 0.2s' in errs
158158

159159

160160
@pytest.mark.requires_external_processes
@@ -284,7 +284,7 @@ def test_multipart_custom_content_type_boundary_added(self, httpbin):
284284
assert r.count(boundary) == 4
285285

286286
def test_multipart_custom_content_type_boundary_preserved(self, httpbin):
287-
# Allow explicit nonsense requests.
287+
# Allow explicit nonsense niquests.
288288
boundary_in_header = 'HEADER_BOUNDARY'
289289
boundary_in_body = 'BODY_BOUNDARY'
290290
r = http(

‎tests/utils/__init__.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
"""Utilities for HTTPie test suite."""
22
import re
33
import shlex
4-
import os
54
import sys
65
import time
76
import json
@@ -31,8 +30,6 @@
3130
HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN = 'pie.dev'
3231
HTTPBIN_WITH_CHUNKED_SUPPORT = 'http://' + HTTPBIN_WITH_CHUNKED_SUPPORT_DOMAIN
3332

34-
IS_PYOPENSSL = os.getenv('HTTPIE_TEST_WITH_PYOPENSSL', '0') == '1'
35-
3633
TESTS_ROOT = Path(__file__).parent.parent
3734
CRLF = '\r\n'
3835
COLOR = '\x1b['
@@ -210,7 +207,7 @@ class BaseCLIResponse:
210207
complete_args: List[str] = []
211208

212209
@property
213-
def command(self):
210+
def command(self): # noqa: F811
214211
cmd = ' '.join(shlex.quote(arg) for arg in ['http', *self.args])
215212
# pytest-httpbin to real httpbin.
216213
return re.sub(r'127\.0\.0\.1:\d+', 'httpbin.org', cmd)

‎tests/utils/matching/parsing.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def make_headers_re(message_type: Expect):
1818
non_crlf = rf'[^{CRLF}]'
1919

2020
# language=RegExp
21-
http_version = r'HTTP/\d+\.\d+'
21+
http_version = r'HTTP/((\d+\.\d+)|\d+)'
2222
if message_type is Expect.REQUEST_HEADERS:
2323
# POST /post HTTP/1.1
2424
start_line_re = fr'{non_crlf}*{http_version}{crlf}'

0 commit comments

Comments
 (0)
Please sign in to comment.