Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/urllib3/poolmanager.py: 35%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

243 statements  

1from __future__ import annotations 

2 

3import functools 

4import logging 

5import typing 

6import warnings 

7from types import TracebackType 

8from urllib.parse import urljoin 

9 

10from ._collections import HTTPHeaderDict, RecentlyUsedContainer 

11from ._request_methods import RequestMethods 

12from .connection import ProxyConfig 

13from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, port_by_scheme 

14from .exceptions import ( 

15 LocationValueError, 

16 MaxRetryError, 

17 ProxySchemeUnknown, 

18 URLSchemeUnknown, 

19) 

20from .response import BaseHTTPResponse 

21from .util.connection import _TYPE_SOCKET_OPTIONS 

22from .util.proxy import connection_requires_http_tunnel 

23from .util.retry import Retry 

24from .util.timeout import Timeout 

25from .util.url import Url, parse_url 

26 

27if typing.TYPE_CHECKING: 

28 import ssl 

29 

30 from typing_extensions import Self 

31 

32__all__ = ["PoolManager", "ProxyManager", "proxy_from_url"] 

33 

34 

35log = logging.getLogger(__name__) 

36 

37SSL_KEYWORDS = ( 

38 "key_file", 

39 "cert_file", 

40 "cert_reqs", 

41 "ca_certs", 

42 "ca_cert_data", 

43 "ssl_version", 

44 "ssl_minimum_version", 

45 "ssl_maximum_version", 

46 "ca_cert_dir", 

47 "ssl_context", 

48 "key_password", 

49 "server_hostname", 

50 "assert_hostname", 

51 "assert_fingerprint", 

52) 

53# Default value for `blocksize` - a new parameter introduced to 

54# http.client.HTTPConnection & http.client.HTTPSConnection in Python 3.7 

55_DEFAULT_BLOCKSIZE = 16384 

56 

57 

58class PoolKey(typing.NamedTuple): 

59 """ 

60 All known keyword arguments that could be provided to the pool manager, its 

61 pools, or the underlying connections. 

62 

63 All custom key schemes should include the fields in this key at a minimum. 

64 """ 

65 

66 key_scheme: str 

67 key_host: str 

68 key_port: int | None 

69 key_timeout: Timeout | float | int | None 

70 key_retries: Retry | bool | int | None 

71 key_block: bool | None 

72 key_source_address: tuple[str, int] | None 

73 key_key_file: str | None 

74 key_key_password: str | None 

75 key_cert_file: str | None 

76 key_cert_reqs: str | None 

77 key_ca_certs: str | None 

78 key_ca_cert_data: str | bytes | None 

79 key_ssl_version: int | str | None 

80 key_ssl_minimum_version: ssl.TLSVersion | None 

81 key_ssl_maximum_version: ssl.TLSVersion | None 

82 key_ca_cert_dir: str | None 

83 key_ssl_context: ssl.SSLContext | None 

84 key_maxsize: int | None 

85 key_headers: frozenset[tuple[str, str]] | None 

86 key__proxy: Url | None 

87 key__proxy_headers: frozenset[tuple[str, str]] | None 

88 key__proxy_config: ProxyConfig | None 

89 key_socket_options: _TYPE_SOCKET_OPTIONS | None 

90 key__socks_options: frozenset[tuple[str, str]] | None 

91 key_assert_hostname: bool | str | None 

92 key_assert_fingerprint: str | None 

93 key_server_hostname: str | None 

94 key_blocksize: int | None 

95 

96 

97def _default_key_normalizer( 

98 key_class: type[PoolKey], request_context: dict[str, typing.Any] 

99) -> PoolKey: 

100 """ 

101 Create a pool key out of a request context dictionary. 

102 

103 According to RFC 3986, both the scheme and host are case-insensitive. 

104 Therefore, this function normalizes both before constructing the pool 

105 key for an HTTPS request. If you wish to change this behaviour, provide 

106 alternate callables to ``key_fn_by_scheme``. 

107 

108 :param key_class: 

109 The class to use when constructing the key. This should be a namedtuple 

110 with the ``scheme`` and ``host`` keys at a minimum. 

111 :type key_class: namedtuple 

112 :param request_context: 

113 A dictionary-like object that contain the context for a request. 

114 :type request_context: dict 

115 

116 :return: A namedtuple that can be used as a connection pool key. 

117 :rtype: PoolKey 

118 """ 

119 # Since we mutate the dictionary, make a copy first 

120 context = request_context.copy() 

121 context["scheme"] = context["scheme"].lower() 

122 context["host"] = context["host"].lower() 

123 

124 # These are both dictionaries and need to be transformed into frozensets 

125 for key in ("headers", "_proxy_headers", "_socks_options"): 

126 if key in context and context[key] is not None: 

127 context[key] = frozenset(context[key].items()) 

128 

129 # The socket_options key may be a list and needs to be transformed into a 

130 # tuple. 

131 socket_opts = context.get("socket_options") 

132 if socket_opts is not None: 

133 context["socket_options"] = tuple(socket_opts) 

134 

135 # Map the kwargs to the names in the namedtuple - this is necessary since 

136 # namedtuples can't have fields starting with '_'. 

137 for key in list(context.keys()): 

138 context["key_" + key] = context.pop(key) 

139 

140 # Default to ``None`` for keys missing from the context 

141 for field in key_class._fields: 

142 if field not in context: 

143 context[field] = None 

144 

145 # Default key_blocksize to _DEFAULT_BLOCKSIZE if missing from the context 

146 if context.get("key_blocksize") is None: 

147 context["key_blocksize"] = _DEFAULT_BLOCKSIZE 

148 

149 return key_class(**context) 

150 

151 

152#: A dictionary that maps a scheme to a callable that creates a pool key. 

153#: This can be used to alter the way pool keys are constructed, if desired. 

154#: Each PoolManager makes a copy of this dictionary so they can be configured 

155#: globally here, or individually on the instance. 

156key_fn_by_scheme = { 

157 "http": functools.partial(_default_key_normalizer, PoolKey), 

158 "https": functools.partial(_default_key_normalizer, PoolKey), 

159} 

160 

161pool_classes_by_scheme = {"http": HTTPConnectionPool, "https": HTTPSConnectionPool} 

162 

163 

164class PoolManager(RequestMethods): 

165 """ 

166 Allows for arbitrary requests while transparently keeping track of 

167 necessary connection pools for you. 

168 

169 :param num_pools: 

170 Number of connection pools to cache before discarding the least 

171 recently used pool. 

172 

173 :param headers: 

174 Headers to include with all requests, unless other headers are given 

175 explicitly. 

176 

177 :param \\**connection_pool_kw: 

178 Additional parameters are used to create fresh 

179 :class:`urllib3.connectionpool.ConnectionPool` instances. 

180 

181 Example: 

182 

183 .. code-block:: python 

184 

185 import urllib3 

186 

187 http = urllib3.PoolManager(num_pools=2) 

188 

189 resp1 = http.request("GET", "https://google.com/") 

190 resp2 = http.request("GET", "https://google.com/mail") 

191 resp3 = http.request("GET", "https://yahoo.com/") 

192 

193 print(len(http.pools)) 

194 # 2 

195 

196 """ 

197 

198 proxy: Url | None = None 

199 proxy_config: ProxyConfig | None = None 

200 

201 def __init__( 

202 self, 

203 num_pools: int = 10, 

204 headers: typing.Mapping[str, str] | None = None, 

205 **connection_pool_kw: typing.Any, 

206 ) -> None: 

207 super().__init__(headers) 

208 # PoolManager handles redirects itself in PoolManager.urlopen(). 

209 # It always passes redirect=False to the underlying connection pool to 

210 # suppress per-pool redirect handling. If the user supplied a non-Retry 

211 # value (int/bool/etc) for retries and we let the pool normalize it 

212 # while redirect=False, the resulting Retry object would have redirect 

213 # handling disabled, which can interfere with PoolManager's own 

214 # redirect logic. Normalize here so redirects remain governed solely by 

215 # PoolManager logic. 

216 if "retries" in connection_pool_kw: 

217 retries = connection_pool_kw["retries"] 

218 if not isinstance(retries, Retry): 

219 retries = Retry.from_int(retries) 

220 connection_pool_kw = connection_pool_kw.copy() 

221 connection_pool_kw["retries"] = retries 

222 self.connection_pool_kw = connection_pool_kw 

223 

224 self.pools: RecentlyUsedContainer[PoolKey, HTTPConnectionPool] 

225 self.pools = RecentlyUsedContainer(num_pools) 

226 

227 # Locally set the pool classes and keys so other PoolManagers can 

228 # override them. 

229 self.pool_classes_by_scheme = pool_classes_by_scheme 

230 self.key_fn_by_scheme = key_fn_by_scheme.copy() 

231 

232 def __enter__(self) -> Self: 

233 return self 

234 

235 def __exit__( 

236 self, 

237 exc_type: type[BaseException] | None, 

238 exc_val: BaseException | None, 

239 exc_tb: TracebackType | None, 

240 ) -> typing.Literal[False]: 

241 self.clear() 

242 # Return False to re-raise any potential exceptions 

243 return False 

244 

245 def _new_pool( 

246 self, 

247 scheme: str, 

248 host: str, 

249 port: int, 

250 request_context: dict[str, typing.Any] | None = None, 

251 ) -> HTTPConnectionPool: 

252 """ 

253 Create a new :class:`urllib3.connectionpool.ConnectionPool` based on host, port, scheme, and 

254 any additional pool keyword arguments. 

255 

256 If ``request_context`` is provided, it is provided as keyword arguments 

257 to the pool class used. This method is used to actually create the 

258 connection pools handed out by :meth:`connection_from_url` and 

259 companion methods. It is intended to be overridden for customization. 

260 """ 

261 pool_cls: type[HTTPConnectionPool] = self.pool_classes_by_scheme[scheme] 

262 if request_context is None: 

263 request_context = self.connection_pool_kw.copy() 

264 

265 # Default blocksize to _DEFAULT_BLOCKSIZE if missing or explicitly 

266 # set to 'None' in the request_context. 

267 if request_context.get("blocksize") is None: 

268 request_context["blocksize"] = _DEFAULT_BLOCKSIZE 

269 

270 # Although the context has everything necessary to create the pool, 

271 # this function has historically only used the scheme, host, and port 

272 # in the positional args. When an API change is acceptable these can 

273 # be removed. 

274 for key in ("scheme", "host", "port"): 

275 request_context.pop(key, None) 

276 

277 if scheme == "http": 

278 for kw in SSL_KEYWORDS: 

279 request_context.pop(kw, None) 

280 

281 return pool_cls(host, port, **request_context) 

282 

283 def clear(self) -> None: 

284 """ 

285 Empty our store of pools and direct them all to close. 

286 

287 This will not affect in-flight connections, but they will not be 

288 re-used after completion. 

289 """ 

290 self.pools.clear() 

291 

292 def connection_from_host( 

293 self, 

294 host: str | None, 

295 port: int | None = None, 

296 scheme: str | None = "http", 

297 pool_kwargs: dict[str, typing.Any] | None = None, 

298 ) -> HTTPConnectionPool: 

299 """ 

300 Get a :class:`urllib3.connectionpool.ConnectionPool` based on the host, port, and scheme. 

301 

302 If ``port`` isn't given, it will be derived from the ``scheme`` using 

303 ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is 

304 provided, it is merged with the instance's ``connection_pool_kw`` 

305 variable and used to create the new connection pool, if one is 

306 needed. 

307 """ 

308 

309 if not host: 

310 raise LocationValueError("No host specified.") 

311 

312 request_context = self._merge_pool_kwargs(pool_kwargs) 

313 request_context["scheme"] = scheme or "http" 

314 if port is None: 

315 port = port_by_scheme.get(request_context["scheme"].lower(), 80) 

316 request_context["port"] = port 

317 request_context["host"] = host 

318 

319 return self.connection_from_context(request_context) 

320 

321 def connection_from_context( 

322 self, request_context: dict[str, typing.Any] 

323 ) -> HTTPConnectionPool: 

324 """ 

325 Get a :class:`urllib3.connectionpool.ConnectionPool` based on the request context. 

326 

327 ``request_context`` must at least contain the ``scheme`` key and its 

328 value must be a key in ``key_fn_by_scheme`` instance variable. 

329 """ 

330 if "strict" in request_context: 

331 warnings.warn( 

332 "The 'strict' parameter is no longer needed on Python 3+. " 

333 "This will raise an error in urllib3 v3.0.", 

334 FutureWarning, 

335 ) 

336 request_context.pop("strict") 

337 

338 scheme = request_context["scheme"].lower() 

339 pool_key_constructor = self.key_fn_by_scheme.get(scheme) 

340 if not pool_key_constructor: 

341 raise URLSchemeUnknown(scheme) 

342 pool_key = pool_key_constructor(request_context) 

343 

344 return self.connection_from_pool_key(pool_key, request_context=request_context) 

345 

346 def connection_from_pool_key( 

347 self, pool_key: PoolKey, request_context: dict[str, typing.Any] 

348 ) -> HTTPConnectionPool: 

349 """ 

350 Get a :class:`urllib3.connectionpool.ConnectionPool` based on the provided pool key. 

351 

352 ``pool_key`` should be a namedtuple that only contains immutable 

353 objects. At a minimum it must have the ``scheme``, ``host``, and 

354 ``port`` fields. 

355 """ 

356 with self.pools.lock: 

357 # If the scheme, host, or port doesn't match existing open 

358 # connections, open a new ConnectionPool. 

359 pool = self.pools.get(pool_key) 

360 if pool: 

361 return pool 

362 

363 # Make a fresh ConnectionPool of the desired type 

364 scheme = request_context["scheme"] 

365 host = request_context["host"] 

366 port = request_context["port"] 

367 pool = self._new_pool(scheme, host, port, request_context=request_context) 

368 self.pools[pool_key] = pool 

369 

370 return pool 

371 

372 def connection_from_url( 

373 self, url: str, pool_kwargs: dict[str, typing.Any] | None = None 

374 ) -> HTTPConnectionPool: 

375 """ 

376 Similar to :func:`urllib3.connectionpool.connection_from_url`. 

377 

378 If ``pool_kwargs`` is not provided and a new pool needs to be 

379 constructed, ``self.connection_pool_kw`` is used to initialize 

380 the :class:`urllib3.connectionpool.ConnectionPool`. If ``pool_kwargs`` 

381 is provided, it is used instead. Note that if a new pool does not 

382 need to be created for the request, the provided ``pool_kwargs`` are 

383 not used. 

384 """ 

385 u = parse_url(url) 

386 return self.connection_from_host( 

387 u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs 

388 ) 

389 

390 def _merge_pool_kwargs( 

391 self, override: dict[str, typing.Any] | None 

392 ) -> dict[str, typing.Any]: 

393 """ 

394 Merge a dictionary of override values for self.connection_pool_kw. 

395 

396 This does not modify self.connection_pool_kw and returns a new dict. 

397 Any keys in the override dictionary with a value of ``None`` are 

398 removed from the merged dictionary. 

399 """ 

400 base_pool_kwargs = self.connection_pool_kw.copy() 

401 if override: 

402 for key, value in override.items(): 

403 if value is None: 

404 try: 

405 del base_pool_kwargs[key] 

406 except KeyError: 

407 pass 

408 else: 

409 base_pool_kwargs[key] = value 

410 return base_pool_kwargs 

411 

412 def _proxy_requires_url_absolute_form(self, parsed_url: Url) -> bool: 

413 """ 

414 Indicates if the proxy requires the complete destination URL in the 

415 request. Normally this is only needed when not using an HTTP CONNECT 

416 tunnel. 

417 """ 

418 if self.proxy is None: 

419 return False 

420 

421 return not connection_requires_http_tunnel( 

422 self.proxy, self.proxy_config, parsed_url.scheme 

423 ) 

424 

425 def urlopen( # type: ignore[override] 

426 self, method: str, url: str, redirect: bool = True, **kw: typing.Any 

427 ) -> BaseHTTPResponse: 

428 """ 

429 Same as :meth:`urllib3.HTTPConnectionPool.urlopen` 

430 with custom cross-host redirect logic and only sends the request-uri 

431 portion of the ``url``. 

432 

433 The given ``url`` parameter must be absolute, such that an appropriate 

434 :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. 

435 """ 

436 u = parse_url(url) 

437 

438 if u.scheme is None: 

439 warnings.warn( 

440 "URLs without a scheme (ie 'https://') are deprecated and will raise an error " 

441 "in urllib3 v3.0. To avoid this FutureWarning ensure all URLs " 

442 "start with 'https://' or 'http://'. Read more in this issue: " 

443 "https://github.com/urllib3/urllib3/issues/2920", 

444 category=FutureWarning, 

445 stacklevel=2, 

446 ) 

447 

448 conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) 

449 

450 kw["assert_same_host"] = False 

451 kw["redirect"] = False 

452 

453 if "headers" not in kw: 

454 kw["headers"] = self.headers 

455 

456 if self._proxy_requires_url_absolute_form(u): 

457 response = conn.urlopen(method, u._replace(fragment=None).url, **kw) 

458 else: 

459 response = conn.urlopen(method, u.request_uri, **kw) 

460 

461 redirect_location = redirect and response.get_redirect_location() 

462 if not redirect_location: 

463 return response 

464 

465 # Support relative URLs for redirecting. 

466 redirect_location = urljoin(url, redirect_location) 

467 

468 if response.status == 303: 

469 # Change the method according to RFC 9110, Section 15.4.4. 

470 method = "GET" 

471 # And lose the body not to transfer anything sensitive. 

472 kw["body"] = None 

473 kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change() 

474 

475 retries = kw.get("retries", response.retries) 

476 if not isinstance(retries, Retry): 

477 retries = Retry.from_int(retries, redirect=redirect) 

478 

479 # Strip headers marked as unsafe to forward to the redirected location. 

480 # Check remove_headers_on_redirect to avoid a potential network call within 

481 # conn.is_same_host() which may use socket.gethostbyname() in the future. 

482 if retries.remove_headers_on_redirect and not conn.is_same_host( 

483 redirect_location 

484 ): 

485 new_headers = kw["headers"].copy() 

486 for header in kw["headers"]: 

487 if header.lower() in retries.remove_headers_on_redirect: 

488 new_headers.pop(header, None) 

489 kw["headers"] = new_headers 

490 

491 try: 

492 retries = retries.increment(method, url, response=response, _pool=conn) 

493 except MaxRetryError: 

494 if retries.raise_on_redirect: 

495 response.drain_conn() 

496 raise 

497 return response 

498 

499 kw["retries"] = retries 

500 kw["redirect"] = redirect 

501 

502 log.info("Redirecting %s -> %s", url, redirect_location) 

503 

504 response.drain_conn() 

505 return self.urlopen(method, redirect_location, **kw) 

506 

507 

508class ProxyManager(PoolManager): 

509 """ 

510 Behaves just like :class:`PoolManager`, but sends all requests through 

511 the defined proxy, using the CONNECT method for HTTPS URLs. 

512 

513 :param proxy_url: 

514 The URL of the proxy to be used. 

515 

516 :param proxy_headers: 

517 A dictionary containing headers that will be sent to the proxy. In case 

518 of HTTP they are being sent with each request, while in the 

519 HTTPS/CONNECT case they are sent only once. Could be used for proxy 

520 authentication. 

521 

522 :param proxy_ssl_context: 

523 The proxy SSL context is used to establish the TLS connection to the 

524 proxy when using HTTPS proxies. 

525 

526 :param use_forwarding_for_https: 

527 (Defaults to False) If set to True will forward requests to the HTTPS 

528 proxy to be made on behalf of the client instead of creating a TLS 

529 tunnel via the CONNECT method. **Enabling this flag means that request 

530 and response headers and content will be visible from the HTTPS proxy** 

531 whereas tunneling keeps request and response headers and content 

532 private. IP address, target hostname, SNI, and port are always visible 

533 to an HTTPS proxy even when this flag is disabled. 

534 

535 :param proxy_assert_hostname: 

536 The hostname of the certificate to verify against. 

537 

538 :param proxy_assert_fingerprint: 

539 The fingerprint of the certificate to verify against. 

540 

541 Example: 

542 

543 .. code-block:: python 

544 

545 import urllib3 

546 

547 proxy = urllib3.ProxyManager("https://localhost:3128/") 

548 

549 resp1 = proxy.request("GET", "http://google.com/") 

550 resp2 = proxy.request("GET", "http://httpbin.org/") 

551 

552 # One pool was shared by both plain HTTP requests. 

553 print(len(proxy.pools)) 

554 # 1 

555 

556 resp3 = proxy.request("GET", "https://httpbin.org/") 

557 resp4 = proxy.request("GET", "https://twitter.com/") 

558 

559 # A separate pool was added for each HTTPS target. 

560 print(len(proxy.pools)) 

561 # 3 

562 

563 """ 

564 

565 def __init__( 

566 self, 

567 proxy_url: str, 

568 num_pools: int = 10, 

569 headers: typing.Mapping[str, str] | None = None, 

570 proxy_headers: typing.Mapping[str, str] | None = None, 

571 proxy_ssl_context: ssl.SSLContext | None = None, 

572 use_forwarding_for_https: bool = False, 

573 proxy_assert_hostname: None | str | typing.Literal[False] = None, 

574 proxy_assert_fingerprint: str | None = None, 

575 **connection_pool_kw: typing.Any, 

576 ) -> None: 

577 if isinstance(proxy_url, HTTPConnectionPool): 

578 str_proxy_url = f"{proxy_url.scheme}://{proxy_url.host}:{proxy_url.port}" 

579 else: 

580 str_proxy_url = proxy_url 

581 proxy = parse_url(str_proxy_url) 

582 

583 if proxy.scheme not in ("http", "https"): 

584 raise ProxySchemeUnknown(proxy.scheme) 

585 

586 if ( 

587 use_forwarding_for_https 

588 and proxy.scheme == "https" 

589 and connection_pool_kw.get("ssl_context") is not None 

590 ): 

591 warnings.warn( 

592 "Passing ssl_context when use_forwarding_for_https=True is deprecated " 

593 "and will raise an error in urllib3 v3.0. " 

594 "Use proxy_ssl_context to configure the TLS connection to the proxy.", 

595 FutureWarning, 

596 stacklevel=2, 

597 ) 

598 if proxy_ssl_context is None: 

599 proxy_ssl_context = connection_pool_kw.get("ssl_context") 

600 

601 if proxy.port is None: 

602 port = port_by_scheme.get(proxy.scheme, 80) 

603 proxy = proxy._replace(port=port) 

604 

605 self.proxy = proxy 

606 self.proxy_headers = proxy_headers or {} 

607 self.proxy_ssl_context = proxy_ssl_context 

608 self.proxy_config = ProxyConfig( 

609 proxy_ssl_context, 

610 use_forwarding_for_https, 

611 proxy_assert_hostname, 

612 proxy_assert_fingerprint, 

613 ) 

614 

615 connection_pool_kw["_proxy"] = self.proxy 

616 connection_pool_kw["_proxy_headers"] = self.proxy_headers 

617 connection_pool_kw["_proxy_config"] = self.proxy_config 

618 

619 super().__init__(num_pools, headers, **connection_pool_kw) 

620 

621 def connection_from_host( 

622 self, 

623 host: str | None, 

624 port: int | None = None, 

625 scheme: str | None = "http", 

626 pool_kwargs: dict[str, typing.Any] | None = None, 

627 ) -> HTTPConnectionPool: 

628 if scheme == "https": 

629 return super().connection_from_host( 

630 host, port, scheme, pool_kwargs=pool_kwargs 

631 ) 

632 

633 return super().connection_from_host( 

634 self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs # type: ignore[union-attr] 

635 ) 

636 

637 def _set_proxy_headers( 

638 self, url: str, headers: typing.Mapping[str, str] | None = None 

639 ) -> typing.Mapping[str, str]: 

640 """ 

641 Sets headers needed by proxies: specifically, the Accept and Host 

642 headers. Only sets headers not provided by the user. 

643 """ 

644 headers_ = {"Accept": "*/*"} 

645 

646 netloc = parse_url(url).netloc 

647 if netloc: 

648 headers_["Host"] = netloc 

649 

650 if headers: 

651 headers_.update(headers) 

652 return headers_ 

653 

654 def urlopen( # type: ignore[override] 

655 self, method: str, url: str, redirect: bool = True, **kw: typing.Any 

656 ) -> BaseHTTPResponse: 

657 "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." 

658 u = parse_url(url) 

659 if not connection_requires_http_tunnel(self.proxy, self.proxy_config, u.scheme): 

660 # For connections using HTTP CONNECT, httplib sets the necessary 

661 # headers on the CONNECT to the proxy. If we're not using CONNECT, 

662 # we'll definitely need to set 'Host' at the very least. 

663 headers = kw.get("headers", self.headers) 

664 kw["headers"] = self._set_proxy_headers(url, headers) 

665 

666 return super().urlopen(method, url, redirect=redirect, **kw) 

667 

668 

669def proxy_from_url(url: str, **kw: typing.Any) -> ProxyManager: 

670 return ProxyManager(proxy_url=url, **kw)