Add automatic code formatting to Requests (#6095)

This commit is contained in:
Nate Prewitt
2022-04-29 13:16:58 -06:00
committed by GitHub
parent 2d5517682b
commit 2a6f290bc0
39 changed files with 2840 additions and 2409 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
[run] [run]
omit = requests/packages/* omit = requests/packages/*
+1 -1
View File
@@ -25,4 +25,4 @@ import requests
This command is only available on Requests v2.16.4 and greater. Otherwise, This command is only available on Requests v2.16.4 and greater. Otherwise,
please provide some basic information about your system (Python version, please provide some basic information about your system (Python version,
operating system, &c). operating system, &c).
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
with: with:
languages: "python" languages: "python"
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file. # By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file. # Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main # queries: ./path/to/local/query, your-org/your-repo/queries@main
+28
View File
@@ -0,0 +1,28 @@
exclude: 'docs/|ext/'
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/PyCQA/isort
rev: 5.10.1
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 22.3.0
hooks:
- id: black
exclude: tests/test_lowlevel.py
- repo: https://github.com/asottile/pyupgrade
rev: v2.31.1
hooks:
- id: pyupgrade
args: [--py37-plus]
- repo: https://gitlab.com/pycqa/flake8
rev: 4.0.1
hooks:
- id: flake8
+15 -56
View File
@@ -93,6 +93,21 @@ event that you object to the code review feedback, you should make your case
clearly and calmly. If, after doing so, the feedback is judged to still apply, clearly and calmly. If, after doing so, the feedback is judged to still apply,
you must either apply the feedback or withdraw your contribution. you must either apply the feedback or withdraw your contribution.
Code Style
~~~~~~~~~~
Requests uses a collection of tools to ensure the code base has a consistent
style as it grows. We have these orchestrated using a tool called
`pre-commit`_. This can be installed locally and run over your changes prior
to opening a PR, and will also be run as part of the CI approval process
before a change is merged.
You can find the full list of formatting requirements specified in the
`.pre-commit-config.yaml`_ at the top level directory of Requests.
.. _pre-commit: https://pre-commit.com/
.. _.pre-commit-config.yaml: https://github.com/psf/requests/blob/main/.pre-commit-config.yaml
New Contributors New Contributors
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
@@ -103,62 +118,6 @@ asking for help.
Please also check the :ref:`early-feedback` section. Please also check the :ref:`early-feedback` section.
Kenneth Reitz's Code Style™
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The Requests codebase uses the `PEP 8`_ code style.
In addition to the standards outlined in PEP 8, we have a few guidelines:
- Line-length can exceed 79 characters, to 100, when convenient.
- Line-length can exceed 100 characters, when doing otherwise would be *terribly* inconvenient.
- Always use single-quoted strings (e.g. ``'#flatearth'``), unless a single-quote occurs within the string.
Additionally, one of the styles that PEP8 recommends for `line continuations`_
completely lacks all sense of taste, and is not to be permitted within
the Requests codebase::
# Aligned with opening delimiter.
foo = long_function_name(var_one, var_two,
var_three, var_four)
No. Just don't. Please. This is much better::
foo = long_function_name(
var_one,
var_two,
var_three,
var_four,
)
Docstrings are to follow the following syntaxes::
def the_earth_is_flat():
"""NASA divided up the seas into thirty-three degrees."""
pass
::
def fibonacci_spiral_tool():
"""With my feet upon the ground I lose myself / between the sounds
and open wide to suck it in. / I feel it move across my skin. / I'm
reaching up and reaching out. / I'm reaching for the random or
whatever will bewilder me. / Whatever will bewilder me. / And
following our will and wind we may just go where no one's been. /
We'll ride the spiral to the end and may just go where no one's
been.
Spiral out. Keep going...
"""
pass
All functions, methods, and classes are to contain docstrings. Object data
model methods (e.g. ``__repr__``) are typically the exception to this rule.
Thanks for helping to make the world a better place!
.. _PEP 8: https://pep8.org/
.. _line continuations: https://www.python.org/dev/peps/pep-0008/#indentation
Documentation Contributions Documentation Contributions
--------------------------- ---------------------------
+13
View File
@@ -0,0 +1,13 @@
[tool.isort]
profile = "black"
src_paths = ["requests", "test"]
honor_noqa = true
[tool.pytest.ini_options]
addopts = "-p no:warnings --doctest-modules"
doctest_optionflags = "NORMALIZE_WHITESPACE ELLIPSIS"
minversion = "6.2"
testpaths = [
"requests",
"tests",
]
-3
View File
@@ -1,3 +0,0 @@
[pytest]
addopts = -p no:warnings --doctest-modules
doctest_optionflags= NORMALIZE_WHITESPACE ELLIPSIS
+59 -31
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
# __ # __
# /__) _ _ _ _ _/ _ # /__) _ _ _ _ _/ _
# / ( (- (/ (/ (- _) / _) # / ( (- (/ (/ (- _) / _)
@@ -40,8 +38,10 @@ is at <https://requests.readthedocs.io>.
:license: Apache 2.0, see LICENSE for more details. :license: Apache 2.0, see LICENSE for more details.
""" """
import urllib3
import warnings import warnings
import urllib3
from .exceptions import RequestsDependencyWarning from .exceptions import RequestsDependencyWarning
try: try:
@@ -54,13 +54,14 @@ try:
except ImportError: except ImportError:
chardet_version = None chardet_version = None
def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version): def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version):
urllib3_version = urllib3_version.split('.') urllib3_version = urllib3_version.split(".")
assert urllib3_version != ['dev'] # Verify urllib3 isn't installed from git. assert urllib3_version != ["dev"] # Verify urllib3 isn't installed from git.
# Sometimes, urllib3 only reports its version as 16.1. # Sometimes, urllib3 only reports its version as 16.1.
if len(urllib3_version) == 2: if len(urllib3_version) == 2:
urllib3_version.append('0') urllib3_version.append("0")
# Check urllib3 for compatibility. # Check urllib3 for compatibility.
major, minor, patch = urllib3_version # noqa: F811 major, minor, patch = urllib3_version # noqa: F811
@@ -72,36 +73,46 @@ def check_compatibility(urllib3_version, chardet_version, charset_normalizer_ver
# Check charset_normalizer for compatibility. # Check charset_normalizer for compatibility.
if chardet_version: if chardet_version:
major, minor, patch = chardet_version.split('.')[:3] major, minor, patch = chardet_version.split(".")[:3]
major, minor, patch = int(major), int(minor), int(patch) major, minor, patch = int(major), int(minor), int(patch)
# chardet_version >= 3.0.2, < 5.0.0 # chardet_version >= 3.0.2, < 5.0.0
assert (3, 0, 2) <= (major, minor, patch) < (5, 0, 0) assert (3, 0, 2) <= (major, minor, patch) < (5, 0, 0)
elif charset_normalizer_version: elif charset_normalizer_version:
major, minor, patch = charset_normalizer_version.split('.')[:3] major, minor, patch = charset_normalizer_version.split(".")[:3]
major, minor, patch = int(major), int(minor), int(patch) major, minor, patch = int(major), int(minor), int(patch)
# charset_normalizer >= 2.0.0 < 3.0.0 # charset_normalizer >= 2.0.0 < 3.0.0
assert (2, 0, 0) <= (major, minor, patch) < (3, 0, 0) assert (2, 0, 0) <= (major, minor, patch) < (3, 0, 0)
else: else:
raise Exception("You need either charset_normalizer or chardet installed") raise Exception("You need either charset_normalizer or chardet installed")
def _check_cryptography(cryptography_version): def _check_cryptography(cryptography_version):
# cryptography < 1.3.4 # cryptography < 1.3.4
try: try:
cryptography_version = list(map(int, cryptography_version.split('.'))) cryptography_version = list(map(int, cryptography_version.split(".")))
except ValueError: except ValueError:
return return
if cryptography_version < [1, 3, 4]: if cryptography_version < [1, 3, 4]:
warning = 'Old version of cryptography ({}) may cause slowdown.'.format(cryptography_version) warning = "Old version of cryptography ({}) may cause slowdown.".format(
cryptography_version
)
warnings.warn(warning, RequestsDependencyWarning) warnings.warn(warning, RequestsDependencyWarning)
# Check imported dependencies for compatibility. # Check imported dependencies for compatibility.
try: try:
check_compatibility(urllib3.__version__, chardet_version, charset_normalizer_version) check_compatibility(
urllib3.__version__, chardet_version, charset_normalizer_version
)
except (AssertionError, ValueError): except (AssertionError, ValueError):
warnings.warn("urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported " warnings.warn(
"version!".format(urllib3.__version__, chardet_version, charset_normalizer_version), "urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported "
RequestsDependencyWarning) "version!".format(
urllib3.__version__, chardet_version, charset_normalizer_version
),
RequestsDependencyWarning,
)
# Attempt to enable urllib3's fallback for SNI support # Attempt to enable urllib3's fallback for SNI support
# if the standard library doesn't support SNI or the # if the standard library doesn't support SNI or the
@@ -114,39 +125,56 @@ try:
if not getattr(ssl, "HAS_SNI", False): if not getattr(ssl, "HAS_SNI", False):
from urllib3.contrib import pyopenssl from urllib3.contrib import pyopenssl
pyopenssl.inject_into_urllib3() pyopenssl.inject_into_urllib3()
# Check cryptography version # Check cryptography version
from cryptography import __version__ as cryptography_version from cryptography import __version__ as cryptography_version
_check_cryptography(cryptography_version) _check_cryptography(cryptography_version)
except ImportError: except ImportError:
pass pass
# urllib3's DependencyWarnings should be silenced. # urllib3's DependencyWarnings should be silenced.
from urllib3.exceptions import DependencyWarning from urllib3.exceptions import DependencyWarning
warnings.simplefilter('ignore', DependencyWarning)
from .__version__ import __title__, __description__, __url__, __version__ warnings.simplefilter("ignore", DependencyWarning)
from .__version__ import __build__, __author__, __author_email__, __license__
from .__version__ import __copyright__, __cake__
from . import utils
from . import packages
from .models import Request, Response, PreparedRequest
from .api import request, get, head, post, patch, put, delete, options
from .sessions import session, Session
from .status_codes import codes
from .exceptions import (
RequestException, Timeout, URLRequired,
TooManyRedirects, HTTPError, ConnectionError,
FileModeWarning, ConnectTimeout, ReadTimeout, JSONDecodeError
)
# Set default logging handler to avoid "No handler found" warnings. # Set default logging handler to avoid "No handler found" warnings.
import logging import logging
from logging import NullHandler from logging import NullHandler
from . import packages, utils
from .__version__ import (
__author__,
__author_email__,
__build__,
__cake__,
__copyright__,
__description__,
__license__,
__title__,
__url__,
__version__,
)
from .api import delete, get, head, options, patch, post, put, request
from .exceptions import (
ConnectionError,
ConnectTimeout,
FileModeWarning,
HTTPError,
JSONDecodeError,
ReadTimeout,
RequestException,
Timeout,
TooManyRedirects,
URLRequired,
)
from .models import PreparedRequest, Request, Response
from .sessions import Session, session
from .status_codes import codes
logging.getLogger(__name__).addHandler(NullHandler()) logging.getLogger(__name__).addHandler(NullHandler())
# FileModeWarnings go off per the default. # FileModeWarnings go off per the default.
warnings.simplefilter('default', FileModeWarning, append=True) warnings.simplefilter("default", FileModeWarning, append=True)
+9 -9
View File
@@ -2,13 +2,13 @@
# |( |- |.| | | |- `-. | `-. # |( |- |.| | | |- `-. | `-.
# ' ' `-' `-`.`-' `-' `-' ' `-' # ' ' `-' `-`.`-' `-' `-' ' `-'
__title__ = 'requests' __title__ = "requests"
__description__ = 'Python HTTP for Humans.' __description__ = "Python HTTP for Humans."
__url__ = 'https://requests.readthedocs.io' __url__ = "https://requests.readthedocs.io"
__version__ = '2.27.1' __version__ = "2.27.1"
__build__ = 0x022701 __build__ = 0x022701
__author__ = 'Kenneth Reitz' __author__ = "Kenneth Reitz"
__author_email__ = 'me@kennethreitz.org' __author_email__ = "me@kennethreitz.org"
__license__ = 'Apache 2.0' __license__ = "Apache 2.0"
__copyright__ = 'Copyright 2022 Kenneth Reitz' __copyright__ = "Copyright 2022 Kenneth Reitz"
__cake__ = u'\u2728 \U0001f370 \u2728' __cake__ = "\u2728 \U0001f370 \u2728"
+2 -4
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
""" """
requests._internal_utils requests._internal_utils
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
@@ -11,7 +9,7 @@ which depend on extremely few external helpers (such as compat)
from .compat import builtin_str from .compat import builtin_str
def to_native_string(string, encoding='ascii'): def to_native_string(string, encoding="ascii"):
"""Given a string object, regardless of type, returns a representation of """Given a string object, regardless of type, returns a representation of
that string in the native string type, encoding and decoding where that string in the native string type, encoding and decoding where
necessary. This assumes ASCII unless told otherwise. necessary. This assumes ASCII unless told otherwise.
@@ -33,7 +31,7 @@ def unicode_is_ascii(u_string):
""" """
assert isinstance(u_string, str) assert isinstance(u_string, str)
try: try:
u_string.encode('ascii') u_string.encode("ascii")
return True return True
except UnicodeEncodeError: except UnicodeEncodeError:
return False return False
+138 -87
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
""" """
requests.adapters requests.adapters
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
@@ -9,58 +7,76 @@ and maintain connections.
""" """
import os.path import os.path
import socket import socket # noqa: F401
from urllib3.poolmanager import PoolManager, proxy_from_url from urllib3.exceptions import ClosedPoolError, ConnectTimeoutError
from urllib3.response import HTTPResponse
from urllib3.util import parse_url
from urllib3.util import Timeout as TimeoutSauce
from urllib3.util.retry import Retry
from urllib3.exceptions import ClosedPoolError
from urllib3.exceptions import ConnectTimeoutError
from urllib3.exceptions import HTTPError as _HTTPError from urllib3.exceptions import HTTPError as _HTTPError
from urllib3.exceptions import InvalidHeader as _InvalidHeader from urllib3.exceptions import InvalidHeader as _InvalidHeader
from urllib3.exceptions import MaxRetryError from urllib3.exceptions import (
from urllib3.exceptions import NewConnectionError LocationValueError,
MaxRetryError,
NewConnectionError,
ProtocolError,
)
from urllib3.exceptions import ProxyError as _ProxyError from urllib3.exceptions import ProxyError as _ProxyError
from urllib3.exceptions import ProtocolError from urllib3.exceptions import ReadTimeoutError, ResponseError
from urllib3.exceptions import ReadTimeoutError
from urllib3.exceptions import SSLError as _SSLError from urllib3.exceptions import SSLError as _SSLError
from urllib3.exceptions import ResponseError from urllib3.poolmanager import PoolManager, proxy_from_url
from urllib3.exceptions import LocationValueError from urllib3.response import HTTPResponse
from urllib3.util import Timeout as TimeoutSauce
from urllib3.util import parse_url
from urllib3.util.retry import Retry
from .models import Response
from .compat import urlparse, basestring
from .utils import (DEFAULT_CA_BUNDLE_PATH, extract_zipped_paths,
get_encoding_from_headers, prepend_scheme_if_needed,
get_auth_from_url, urldefragauth, select_proxy)
from .structures import CaseInsensitiveDict
from .cookies import extract_cookies_to_jar
from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError,
ProxyError, RetryError, InvalidSchema, InvalidProxyURL,
InvalidURL, InvalidHeader)
from .auth import _basic_auth_str from .auth import _basic_auth_str
from .compat import basestring, urlparse
from .cookies import extract_cookies_to_jar
from .exceptions import (
ConnectionError,
ConnectTimeout,
InvalidHeader,
InvalidProxyURL,
InvalidSchema,
InvalidURL,
ProxyError,
ReadTimeout,
RetryError,
SSLError,
)
from .models import Response
from .structures import CaseInsensitiveDict
from .utils import (
DEFAULT_CA_BUNDLE_PATH,
extract_zipped_paths,
get_auth_from_url,
get_encoding_from_headers,
prepend_scheme_if_needed,
select_proxy,
urldefragauth,
)
try: try:
from urllib3.contrib.socks import SOCKSProxyManager from urllib3.contrib.socks import SOCKSProxyManager
except ImportError: except ImportError:
def SOCKSProxyManager(*args, **kwargs): def SOCKSProxyManager(*args, **kwargs):
raise InvalidSchema("Missing dependencies for SOCKS support.") raise InvalidSchema("Missing dependencies for SOCKS support.")
DEFAULT_POOLBLOCK = False DEFAULT_POOLBLOCK = False
DEFAULT_POOLSIZE = 10 DEFAULT_POOLSIZE = 10
DEFAULT_RETRIES = 0 DEFAULT_RETRIES = 0
DEFAULT_POOL_TIMEOUT = None DEFAULT_POOL_TIMEOUT = None
class BaseAdapter(object): class BaseAdapter:
"""The Base Transport Adapter""" """The Base Transport Adapter"""
def __init__(self): def __init__(self):
super(BaseAdapter, self).__init__() super().__init__()
def send(self, request, stream=False, timeout=None, verify=True, def send(
cert=None, proxies=None): self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
):
"""Sends PreparedRequest object. Returns Response object. """Sends PreparedRequest object. Returns Response object.
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent. :param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
@@ -108,12 +124,22 @@ class HTTPAdapter(BaseAdapter):
>>> a = requests.adapters.HTTPAdapter(max_retries=3) >>> a = requests.adapters.HTTPAdapter(max_retries=3)
>>> s.mount('http://', a) >>> s.mount('http://', a)
""" """
__attrs__ = ['max_retries', 'config', '_pool_connections', '_pool_maxsize',
'_pool_block']
def __init__(self, pool_connections=DEFAULT_POOLSIZE, __attrs__ = [
pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES, "max_retries",
pool_block=DEFAULT_POOLBLOCK): "config",
"_pool_connections",
"_pool_maxsize",
"_pool_block",
]
def __init__(
self,
pool_connections=DEFAULT_POOLSIZE,
pool_maxsize=DEFAULT_POOLSIZE,
max_retries=DEFAULT_RETRIES,
pool_block=DEFAULT_POOLBLOCK,
):
if max_retries == DEFAULT_RETRIES: if max_retries == DEFAULT_RETRIES:
self.max_retries = Retry(0, read=False) self.max_retries = Retry(0, read=False)
else: else:
@@ -121,7 +147,7 @@ class HTTPAdapter(BaseAdapter):
self.config = {} self.config = {}
self.proxy_manager = {} self.proxy_manager = {}
super(HTTPAdapter, self).__init__() super().__init__()
self._pool_connections = pool_connections self._pool_connections = pool_connections
self._pool_maxsize = pool_maxsize self._pool_maxsize = pool_maxsize
@@ -141,10 +167,13 @@ class HTTPAdapter(BaseAdapter):
for attr, value in state.items(): for attr, value in state.items():
setattr(self, attr, value) setattr(self, attr, value)
self.init_poolmanager(self._pool_connections, self._pool_maxsize, self.init_poolmanager(
block=self._pool_block) self._pool_connections, self._pool_maxsize, block=self._pool_block
)
def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs): def init_poolmanager(
self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs
):
"""Initializes a urllib3 PoolManager. """Initializes a urllib3 PoolManager.
This method should not be called from user code, and is only This method should not be called from user code, and is only
@@ -161,8 +190,13 @@ class HTTPAdapter(BaseAdapter):
self._pool_maxsize = maxsize self._pool_maxsize = maxsize
self._pool_block = block self._pool_block = block
self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize, self.poolmanager = PoolManager(
block=block, strict=True, **pool_kwargs) num_pools=connections,
maxsize=maxsize,
block=block,
strict=True,
**pool_kwargs,
)
def proxy_manager_for(self, proxy, **proxy_kwargs): def proxy_manager_for(self, proxy, **proxy_kwargs):
"""Return urllib3 ProxyManager for the given proxy. """Return urllib3 ProxyManager for the given proxy.
@@ -178,7 +212,7 @@ class HTTPAdapter(BaseAdapter):
""" """
if proxy in self.proxy_manager: if proxy in self.proxy_manager:
manager = self.proxy_manager[proxy] manager = self.proxy_manager[proxy]
elif proxy.lower().startswith('socks'): elif proxy.lower().startswith("socks"):
username, password = get_auth_from_url(proxy) username, password = get_auth_from_url(proxy)
manager = self.proxy_manager[proxy] = SOCKSProxyManager( manager = self.proxy_manager[proxy] = SOCKSProxyManager(
proxy, proxy,
@@ -187,7 +221,7 @@ class HTTPAdapter(BaseAdapter):
num_pools=self._pool_connections, num_pools=self._pool_connections,
maxsize=self._pool_maxsize, maxsize=self._pool_maxsize,
block=self._pool_block, block=self._pool_block,
**proxy_kwargs **proxy_kwargs,
) )
else: else:
proxy_headers = self.proxy_headers(proxy) proxy_headers = self.proxy_headers(proxy)
@@ -197,7 +231,8 @@ class HTTPAdapter(BaseAdapter):
num_pools=self._pool_connections, num_pools=self._pool_connections,
maxsize=self._pool_maxsize, maxsize=self._pool_maxsize,
block=self._pool_block, block=self._pool_block,
**proxy_kwargs) **proxy_kwargs,
)
return manager return manager
@@ -213,7 +248,7 @@ class HTTPAdapter(BaseAdapter):
to a CA bundle to use to a CA bundle to use
:param cert: The SSL certificate to verify. :param cert: The SSL certificate to verify.
""" """
if url.lower().startswith('https') and verify: if url.lower().startswith("https") and verify:
cert_loc = None cert_loc = None
@@ -225,17 +260,19 @@ class HTTPAdapter(BaseAdapter):
cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH) cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH)
if not cert_loc or not os.path.exists(cert_loc): if not cert_loc or not os.path.exists(cert_loc):
raise IOError("Could not find a suitable TLS CA certificate bundle, " raise OSError(
"invalid path: {}".format(cert_loc)) f"Could not find a suitable TLS CA certificate bundle, "
f"invalid path: {cert_loc}"
)
conn.cert_reqs = 'CERT_REQUIRED' conn.cert_reqs = "CERT_REQUIRED"
if not os.path.isdir(cert_loc): if not os.path.isdir(cert_loc):
conn.ca_certs = cert_loc conn.ca_certs = cert_loc
else: else:
conn.ca_cert_dir = cert_loc conn.ca_cert_dir = cert_loc
else: else:
conn.cert_reqs = 'CERT_NONE' conn.cert_reqs = "CERT_NONE"
conn.ca_certs = None conn.ca_certs = None
conn.ca_cert_dir = None conn.ca_cert_dir = None
@@ -247,11 +284,14 @@ class HTTPAdapter(BaseAdapter):
conn.cert_file = cert conn.cert_file = cert
conn.key_file = None conn.key_file = None
if conn.cert_file and not os.path.exists(conn.cert_file): if conn.cert_file and not os.path.exists(conn.cert_file):
raise IOError("Could not find the TLS certificate file, " raise OSError(
"invalid path: {}".format(conn.cert_file)) f"Could not find the TLS certificate file, "
f"invalid path: {conn.cert_file}"
)
if conn.key_file and not os.path.exists(conn.key_file): if conn.key_file and not os.path.exists(conn.key_file):
raise IOError("Could not find the TLS key file, " raise OSError(
"invalid path: {}".format(conn.key_file)) f"Could not find the TLS key file, invalid path: {conn.key_file}"
)
def build_response(self, req, resp): def build_response(self, req, resp):
"""Builds a :class:`Response <requests.Response>` object from a urllib3 """Builds a :class:`Response <requests.Response>` object from a urllib3
@@ -266,10 +306,10 @@ class HTTPAdapter(BaseAdapter):
response = Response() response = Response()
# Fallback to None if there's no status_code, for whatever reason. # Fallback to None if there's no status_code, for whatever reason.
response.status_code = getattr(resp, 'status', None) response.status_code = getattr(resp, "status", None)
# Make headers case-insensitive. # Make headers case-insensitive.
response.headers = CaseInsensitiveDict(getattr(resp, 'headers', {})) response.headers = CaseInsensitiveDict(getattr(resp, "headers", {}))
# Set encoding. # Set encoding.
response.encoding = get_encoding_from_headers(response.headers) response.encoding = get_encoding_from_headers(response.headers)
@@ -277,7 +317,7 @@ class HTTPAdapter(BaseAdapter):
response.reason = response.raw.reason response.reason = response.raw.reason
if isinstance(req.url, bytes): if isinstance(req.url, bytes):
response.url = req.url.decode('utf-8') response.url = req.url.decode("utf-8")
else: else:
response.url = req.url response.url = req.url
@@ -302,11 +342,13 @@ class HTTPAdapter(BaseAdapter):
proxy = select_proxy(url, proxies) proxy = select_proxy(url, proxies)
if proxy: if proxy:
proxy = prepend_scheme_if_needed(proxy, 'http') proxy = prepend_scheme_if_needed(proxy, "http")
proxy_url = parse_url(proxy) proxy_url = parse_url(proxy)
if not proxy_url.host: if not proxy_url.host:
raise InvalidProxyURL("Please check proxy URL. It is malformed" raise InvalidProxyURL(
" and could be missing the host.") "Please check proxy URL. It is malformed "
"and could be missing the host."
)
proxy_manager = self.proxy_manager_for(proxy) proxy_manager = self.proxy_manager_for(proxy)
conn = proxy_manager.connection_from_url(url) conn = proxy_manager.connection_from_url(url)
else: else:
@@ -344,11 +386,11 @@ class HTTPAdapter(BaseAdapter):
proxy = select_proxy(request.url, proxies) proxy = select_proxy(request.url, proxies)
scheme = urlparse(request.url).scheme scheme = urlparse(request.url).scheme
is_proxied_http_request = (proxy and scheme != 'https') is_proxied_http_request = proxy and scheme != "https"
using_socks_proxy = False using_socks_proxy = False
if proxy: if proxy:
proxy_scheme = urlparse(proxy).scheme.lower() proxy_scheme = urlparse(proxy).scheme.lower()
using_socks_proxy = proxy_scheme.startswith('socks') using_socks_proxy = proxy_scheme.startswith("socks")
url = request.path_url url = request.path_url
if is_proxied_http_request and not using_socks_proxy: if is_proxied_http_request and not using_socks_proxy:
@@ -387,12 +429,13 @@ class HTTPAdapter(BaseAdapter):
username, password = get_auth_from_url(proxy) username, password = get_auth_from_url(proxy)
if username: if username:
headers['Proxy-Authorization'] = _basic_auth_str(username, headers["Proxy-Authorization"] = _basic_auth_str(username, password)
password)
return headers return headers
def send(self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None): def send(
self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
):
"""Sends PreparedRequest object. Returns Response object. """Sends PreparedRequest object. Returns Response object.
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent. :param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
@@ -416,20 +459,26 @@ class HTTPAdapter(BaseAdapter):
self.cert_verify(conn, request.url, verify, cert) self.cert_verify(conn, request.url, verify, cert)
url = self.request_url(request, proxies) url = self.request_url(request, proxies)
self.add_headers(request, stream=stream, timeout=timeout, verify=verify, cert=cert, proxies=proxies) self.add_headers(
request,
stream=stream,
timeout=timeout,
verify=verify,
cert=cert,
proxies=proxies,
)
chunked = not (request.body is None or 'Content-Length' in request.headers) chunked = not (request.body is None or "Content-Length" in request.headers)
if isinstance(timeout, tuple): if isinstance(timeout, tuple):
try: try:
connect, read = timeout connect, read = timeout
timeout = TimeoutSauce(connect=connect, read=read) timeout = TimeoutSauce(connect=connect, read=read)
except ValueError as e: except ValueError:
# this may raise a string formatting error. raise ValueError(
err = ("Invalid timeout {}. Pass a (connect, read) " f"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, "
"timeout tuple, or a single float to set " f"or a single float to set both timeouts to the same value."
"both timeouts to the same value".format(timeout)) )
raise ValueError(err)
elif isinstance(timeout, TimeoutSauce): elif isinstance(timeout, TimeoutSauce):
pass pass
else: else:
@@ -447,22 +496,24 @@ class HTTPAdapter(BaseAdapter):
preload_content=False, preload_content=False,
decode_content=False, decode_content=False,
retries=self.max_retries, retries=self.max_retries,
timeout=timeout timeout=timeout,
) )
# Send the request. # Send the request.
else: else:
if hasattr(conn, 'proxy_pool'): if hasattr(conn, "proxy_pool"):
conn = conn.proxy_pool conn = conn.proxy_pool
low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT) low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)
try: try:
skip_host = 'Host' in request.headers skip_host = "Host" in request.headers
low_conn.putrequest(request.method, low_conn.putrequest(
url, request.method,
skip_accept_encoding=True, url,
skip_host=skip_host) skip_accept_encoding=True,
skip_host=skip_host,
)
for header, value in request.headers.items(): for header, value in request.headers.items():
low_conn.putheader(header, value) low_conn.putheader(header, value)
@@ -470,11 +521,11 @@ class HTTPAdapter(BaseAdapter):
low_conn.endheaders() low_conn.endheaders()
for i in request.body: for i in request.body:
low_conn.send(hex(len(i))[2:].encode('utf-8')) low_conn.send(hex(len(i))[2:].encode("utf-8"))
low_conn.send(b'\r\n') low_conn.send(b"\r\n")
low_conn.send(i) low_conn.send(i)
low_conn.send(b'\r\n') low_conn.send(b"\r\n")
low_conn.send(b'0\r\n\r\n') low_conn.send(b"0\r\n\r\n")
# Receive the response from the server # Receive the response from the server
r = low_conn.getresponse() r = low_conn.getresponse()
@@ -484,15 +535,15 @@ class HTTPAdapter(BaseAdapter):
pool=conn, pool=conn,
connection=low_conn, connection=low_conn,
preload_content=False, preload_content=False,
decode_content=False decode_content=False,
) )
except: except Exception:
# If we hit any problems here, clean up the connection. # If we hit any problems here, clean up the connection.
# Then, reraise so that we can handle the actual exception. # Then, raise so that we can handle the actual exception.
low_conn.close() low_conn.close()
raise raise
except (ProtocolError, socket.error) as err: except (ProtocolError, OSError) as err:
raise ConnectionError(err, request=request) raise ConnectionError(err, request=request)
except MaxRetryError as e: except MaxRetryError as e:
+8 -10
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
""" """
requests.api requests.api
~~~~~~~~~~~~ ~~~~~~~~~~~~
@@ -72,7 +70,7 @@ def get(url, params=None, **kwargs):
:rtype: requests.Response :rtype: requests.Response
""" """
return request('get', url, params=params, **kwargs) return request("get", url, params=params, **kwargs)
def options(url, **kwargs): def options(url, **kwargs):
@@ -84,7 +82,7 @@ def options(url, **kwargs):
:rtype: requests.Response :rtype: requests.Response
""" """
return request('options', url, **kwargs) return request("options", url, **kwargs)
def head(url, **kwargs): def head(url, **kwargs):
@@ -98,8 +96,8 @@ def head(url, **kwargs):
:rtype: requests.Response :rtype: requests.Response
""" """
kwargs.setdefault('allow_redirects', False) kwargs.setdefault("allow_redirects", False)
return request('head', url, **kwargs) return request("head", url, **kwargs)
def post(url, data=None, json=None, **kwargs): def post(url, data=None, json=None, **kwargs):
@@ -114,7 +112,7 @@ def post(url, data=None, json=None, **kwargs):
:rtype: requests.Response :rtype: requests.Response
""" """
return request('post', url, data=data, json=json, **kwargs) return request("post", url, data=data, json=json, **kwargs)
def put(url, data=None, **kwargs): def put(url, data=None, **kwargs):
@@ -129,7 +127,7 @@ def put(url, data=None, **kwargs):
:rtype: requests.Response :rtype: requests.Response
""" """
return request('put', url, data=data, **kwargs) return request("put", url, data=data, **kwargs)
def patch(url, data=None, **kwargs): def patch(url, data=None, **kwargs):
@@ -144,7 +142,7 @@ def patch(url, data=None, **kwargs):
:rtype: requests.Response :rtype: requests.Response
""" """
return request('patch', url, data=data, **kwargs) return request("patch", url, data=data, **kwargs)
def delete(url, **kwargs): def delete(url, **kwargs):
@@ -156,4 +154,4 @@ def delete(url, **kwargs):
:rtype: requests.Response :rtype: requests.Response
""" """
return request('delete', url, **kwargs) return request("delete", url, **kwargs)
+83 -73
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
""" """
requests.auth requests.auth
~~~~~~~~~~~~~ ~~~~~~~~~~~~~
@@ -7,22 +5,21 @@ requests.auth
This module contains the authentication handlers for Requests. This module contains the authentication handlers for Requests.
""" """
import hashlib
import os import os
import re import re
import time
import hashlib
import threading import threading
import time
import warnings import warnings
from base64 import b64encode from base64 import b64encode
from .compat import urlparse, str, basestring
from .cookies import extract_cookies_to_jar
from ._internal_utils import to_native_string from ._internal_utils import to_native_string
from .compat import basestring, str, urlparse
from .cookies import extract_cookies_to_jar
from .utils import parse_dict_header from .utils import parse_dict_header
CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"
CONTENT_TYPE_MULTI_PART = 'multipart/form-data' CONTENT_TYPE_MULTI_PART = "multipart/form-data"
def _basic_auth_str(username, password): def _basic_auth_str(username, password):
@@ -57,23 +54,23 @@ def _basic_auth_str(username, password):
# -- End Removal -- # -- End Removal --
if isinstance(username, str): if isinstance(username, str):
username = username.encode('latin1') username = username.encode("latin1")
if isinstance(password, str): if isinstance(password, str):
password = password.encode('latin1') password = password.encode("latin1")
authstr = 'Basic ' + to_native_string( authstr = "Basic " + to_native_string(
b64encode(b':'.join((username, password))).strip() b64encode(b":".join((username, password))).strip()
) )
return authstr return authstr
class AuthBase(object): class AuthBase:
"""Base class that all auth implementations derive from""" """Base class that all auth implementations derive from"""
def __call__(self, r): def __call__(self, r):
raise NotImplementedError('Auth hooks must be callable.') raise NotImplementedError("Auth hooks must be callable.")
class HTTPBasicAuth(AuthBase): class HTTPBasicAuth(AuthBase):
@@ -84,16 +81,18 @@ class HTTPBasicAuth(AuthBase):
self.password = password self.password = password
def __eq__(self, other): def __eq__(self, other):
return all([ return all(
self.username == getattr(other, 'username', None), [
self.password == getattr(other, 'password', None) self.username == getattr(other, "username", None),
]) self.password == getattr(other, "password", None),
]
)
def __ne__(self, other): def __ne__(self, other):
return not self == other return not self == other
def __call__(self, r): def __call__(self, r):
r.headers['Authorization'] = _basic_auth_str(self.username, self.password) r.headers["Authorization"] = _basic_auth_str(self.username, self.password)
return r return r
@@ -101,7 +100,7 @@ class HTTPProxyAuth(HTTPBasicAuth):
"""Attaches HTTP Proxy Authentication to a given Request object.""" """Attaches HTTP Proxy Authentication to a given Request object."""
def __call__(self, r): def __call__(self, r):
r.headers['Proxy-Authorization'] = _basic_auth_str(self.username, self.password) r.headers["Proxy-Authorization"] = _basic_auth_str(self.username, self.password)
return r return r
@@ -116,9 +115,9 @@ class HTTPDigestAuth(AuthBase):
def init_per_thread_state(self): def init_per_thread_state(self):
# Ensure state is initialized just once per-thread # Ensure state is initialized just once per-thread
if not hasattr(self._thread_local, 'init'): if not hasattr(self._thread_local, "init"):
self._thread_local.init = True self._thread_local.init = True
self._thread_local.last_nonce = '' self._thread_local.last_nonce = ""
self._thread_local.nonce_count = 0 self._thread_local.nonce_count = 0
self._thread_local.chal = {} self._thread_local.chal = {}
self._thread_local.pos = None self._thread_local.pos = None
@@ -129,44 +128,52 @@ class HTTPDigestAuth(AuthBase):
:rtype: str :rtype: str
""" """
realm = self._thread_local.chal['realm'] realm = self._thread_local.chal["realm"]
nonce = self._thread_local.chal['nonce'] nonce = self._thread_local.chal["nonce"]
qop = self._thread_local.chal.get('qop') qop = self._thread_local.chal.get("qop")
algorithm = self._thread_local.chal.get('algorithm') algorithm = self._thread_local.chal.get("algorithm")
opaque = self._thread_local.chal.get('opaque') opaque = self._thread_local.chal.get("opaque")
hash_utf8 = None hash_utf8 = None
if algorithm is None: if algorithm is None:
_algorithm = 'MD5' _algorithm = "MD5"
else: else:
_algorithm = algorithm.upper() _algorithm = algorithm.upper()
# lambdas assume digest modules are imported at the top level # lambdas assume digest modules are imported at the top level
if _algorithm == 'MD5' or _algorithm == 'MD5-SESS': if _algorithm == "MD5" or _algorithm == "MD5-SESS":
def md5_utf8(x): def md5_utf8(x):
if isinstance(x, str): if isinstance(x, str):
x = x.encode('utf-8') x = x.encode("utf-8")
return hashlib.md5(x).hexdigest() return hashlib.md5(x).hexdigest()
hash_utf8 = md5_utf8 hash_utf8 = md5_utf8
elif _algorithm == 'SHA': elif _algorithm == "SHA":
def sha_utf8(x): def sha_utf8(x):
if isinstance(x, str): if isinstance(x, str):
x = x.encode('utf-8') x = x.encode("utf-8")
return hashlib.sha1(x).hexdigest() return hashlib.sha1(x).hexdigest()
hash_utf8 = sha_utf8 hash_utf8 = sha_utf8
elif _algorithm == 'SHA-256': elif _algorithm == "SHA-256":
def sha256_utf8(x): def sha256_utf8(x):
if isinstance(x, str): if isinstance(x, str):
x = x.encode('utf-8') x = x.encode("utf-8")
return hashlib.sha256(x).hexdigest() return hashlib.sha256(x).hexdigest()
hash_utf8 = sha256_utf8 hash_utf8 = sha256_utf8
elif _algorithm == 'SHA-512': elif _algorithm == "SHA-512":
def sha512_utf8(x): def sha512_utf8(x):
if isinstance(x, str): if isinstance(x, str):
x = x.encode('utf-8') x = x.encode("utf-8")
return hashlib.sha512(x).hexdigest() return hashlib.sha512(x).hexdigest()
hash_utf8 = sha512_utf8 hash_utf8 = sha512_utf8
KD = lambda s, d: hash_utf8("%s:%s" % (s, d)) KD = lambda s, d: hash_utf8(f"{s}:{d}") # noqa:E731
if hash_utf8 is None: if hash_utf8 is None:
return None return None
@@ -177,10 +184,10 @@ class HTTPDigestAuth(AuthBase):
#: path is request-uri defined in RFC 2616 which should not be empty #: path is request-uri defined in RFC 2616 which should not be empty
path = p_parsed.path or "/" path = p_parsed.path or "/"
if p_parsed.query: if p_parsed.query:
path += '?' + p_parsed.query path += f"?{p_parsed.query}"
A1 = '%s:%s:%s' % (self.username, realm, self.password) A1 = f"{self.username}:{realm}:{self.password}"
A2 = '%s:%s' % (method, path) A2 = f"{method}:{path}"
HA1 = hash_utf8(A1) HA1 = hash_utf8(A1)
HA2 = hash_utf8(A2) HA2 = hash_utf8(A2)
@@ -189,22 +196,20 @@ class HTTPDigestAuth(AuthBase):
self._thread_local.nonce_count += 1 self._thread_local.nonce_count += 1
else: else:
self._thread_local.nonce_count = 1 self._thread_local.nonce_count = 1
ncvalue = '%08x' % self._thread_local.nonce_count ncvalue = f"{self._thread_local.nonce_count:08x}"
s = str(self._thread_local.nonce_count).encode('utf-8') s = str(self._thread_local.nonce_count).encode("utf-8")
s += nonce.encode('utf-8') s += nonce.encode("utf-8")
s += time.ctime().encode('utf-8') s += time.ctime().encode("utf-8")
s += os.urandom(8) s += os.urandom(8)
cnonce = (hashlib.sha1(s).hexdigest()[:16]) cnonce = hashlib.sha1(s).hexdigest()[:16]
if _algorithm == 'MD5-SESS': if _algorithm == "MD5-SESS":
HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce)) HA1 = hash_utf8(f"{HA1}:{nonce}:{cnonce}")
if not qop: if not qop:
respdig = KD(HA1, "%s:%s" % (nonce, HA2)) respdig = KD(HA1, f"{nonce}:{HA2}")
elif qop == 'auth' or 'auth' in qop.split(','): elif qop == "auth" or "auth" in qop.split(","):
noncebit = "%s:%s:%s:%s:%s" % ( noncebit = f"{nonce}:{ncvalue}:{cnonce}:auth:{HA2}"
nonce, ncvalue, cnonce, 'auth', HA2
)
respdig = KD(HA1, noncebit) respdig = KD(HA1, noncebit)
else: else:
# XXX handle auth-int. # XXX handle auth-int.
@@ -213,18 +218,20 @@ class HTTPDigestAuth(AuthBase):
self._thread_local.last_nonce = nonce self._thread_local.last_nonce = nonce
# XXX should the partial digests be encoded too? # XXX should the partial digests be encoded too?
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \ base = (
'response="%s"' % (self.username, realm, nonce, path, respdig) f'username="{self.username}", realm="{realm}", nonce="{nonce}", '
f'uri="{path}", response="{respdig}"'
)
if opaque: if opaque:
base += ', opaque="%s"' % opaque base += f', opaque="{opaque}"'
if algorithm: if algorithm:
base += ', algorithm="%s"' % algorithm base += f', algorithm="{algorithm}"'
if entdig: if entdig:
base += ', digest="%s"' % entdig base += f', digest="{entdig}"'
if qop: if qop:
base += ', qop="auth", nc=%s, cnonce="%s"' % (ncvalue, cnonce) base += f', qop="auth", nc={ncvalue}, cnonce="{cnonce}"'
return 'Digest %s' % (base) return f"Digest {base}"
def handle_redirect(self, r, **kwargs): def handle_redirect(self, r, **kwargs):
"""Reset num_401_calls counter on redirects.""" """Reset num_401_calls counter on redirects."""
@@ -248,13 +255,13 @@ class HTTPDigestAuth(AuthBase):
# Rewind the file position indicator of the body to where # Rewind the file position indicator of the body to where
# it was to resend the request. # it was to resend the request.
r.request.body.seek(self._thread_local.pos) r.request.body.seek(self._thread_local.pos)
s_auth = r.headers.get('www-authenticate', '') s_auth = r.headers.get("www-authenticate", "")
if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2: if "digest" in s_auth.lower() and self._thread_local.num_401_calls < 2:
self._thread_local.num_401_calls += 1 self._thread_local.num_401_calls += 1
pat = re.compile(r'digest ', flags=re.IGNORECASE) pat = re.compile(r"digest ", flags=re.IGNORECASE)
self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1)) self._thread_local.chal = parse_dict_header(pat.sub("", s_auth, count=1))
# Consume content and release the original connection # Consume content and release the original connection
# to allow our new request to reuse the same one. # to allow our new request to reuse the same one.
@@ -264,8 +271,9 @@ class HTTPDigestAuth(AuthBase):
extract_cookies_to_jar(prep._cookies, r.request, r.raw) extract_cookies_to_jar(prep._cookies, r.request, r.raw)
prep.prepare_cookies(prep._cookies) prep.prepare_cookies(prep._cookies)
prep.headers['Authorization'] = self.build_digest_header( prep.headers["Authorization"] = self.build_digest_header(
prep.method, prep.url) prep.method, prep.url
)
_r = r.connection.send(prep, **kwargs) _r = r.connection.send(prep, **kwargs)
_r.history.append(r) _r.history.append(r)
_r.request = prep _r.request = prep
@@ -280,7 +288,7 @@ class HTTPDigestAuth(AuthBase):
self.init_per_thread_state() self.init_per_thread_state()
# If we have a saved nonce, skip the 401 # If we have a saved nonce, skip the 401
if self._thread_local.last_nonce: if self._thread_local.last_nonce:
r.headers['Authorization'] = self.build_digest_header(r.method, r.url) r.headers["Authorization"] = self.build_digest_header(r.method, r.url)
try: try:
self._thread_local.pos = r.body.tell() self._thread_local.pos = r.body.tell()
except AttributeError: except AttributeError:
@@ -289,17 +297,19 @@ class HTTPDigestAuth(AuthBase):
# file position of the previous body. Ensure it's set to # file position of the previous body. Ensure it's set to
# None. # None.
self._thread_local.pos = None self._thread_local.pos = None
r.register_hook('response', self.handle_401) r.register_hook("response", self.handle_401)
r.register_hook('response', self.handle_redirect) r.register_hook("response", self.handle_redirect)
self._thread_local.num_401_calls = 1 self._thread_local.num_401_calls = 1
return r return r
def __eq__(self, other): def __eq__(self, other):
return all([ return all(
self.username == getattr(other, 'username', None), [
self.password == getattr(other, 'password', None) self.username == getattr(other, "username", None),
]) self.password == getattr(other, "password", None),
]
)
def __ne__(self, other): def __ne__(self, other):
return not self == other return not self == other
+1 -2
View File
@@ -1,5 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*-
""" """
requests.certs requests.certs
@@ -14,5 +13,5 @@ packaged CA bundle.
""" """
from certifi import where from certifi import where
if __name__ == '__main__': if __name__ == "__main__":
print(where()) print(where())
+28 -12
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
""" """
requests.compat requests.compat
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
@@ -24,15 +22,16 @@ import sys
_ver = sys.version_info _ver = sys.version_info
#: Python 2.x? #: Python 2.x?
is_py2 = (_ver[0] == 2) is_py2 = _ver[0] == 2
#: Python 3.x? #: Python 3.x?
is_py3 = (_ver[0] == 3) is_py3 = _ver[0] == 3
# json/simplejson module import resolution # json/simplejson module import resolution
has_simplejson = False has_simplejson = False
try: try:
import simplejson as json import simplejson as json
has_simplejson = True has_simplejson = True
except ImportError: except ImportError:
import json import json
@@ -42,18 +41,35 @@ if has_simplejson:
else: else:
from json import JSONDecodeError from json import JSONDecodeError
# --------- # Keep OrderedDict for backwards compatibility.
# Legacy Imports from collections import OrderedDict
# --------- from collections.abc import Callable, Mapping, MutableMapping
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag
from urllib.request import parse_http_list, getproxies, proxy_bypass, proxy_bypass_environment, getproxies_environment
from http import cookiejar as cookielib from http import cookiejar as cookielib
from http.cookies import Morsel from http.cookies import Morsel
from io import StringIO from io import StringIO
# Keep OrderedDict for backwards compatibility. # --------------
from collections import OrderedDict # Legacy Imports
from collections.abc import Callable, Mapping, MutableMapping # --------------
from urllib.parse import (
quote,
quote_plus,
unquote,
unquote_plus,
urldefrag,
urlencode,
urljoin,
urlparse,
urlsplit,
urlunparse,
)
from urllib.request import (
getproxies,
getproxies_environment,
parse_http_list,
proxy_bypass,
proxy_bypass_environment,
)
builtin_str = str builtin_str = str
str = str str = str
+82 -70
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
""" """
requests.cookies requests.cookies
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
@@ -9,12 +7,12 @@ Compatibility code to be able to use `cookielib.CookieJar` with requests.
requests.utils imports from here, so be careful with imports. requests.utils imports from here, so be careful with imports.
""" """
import calendar
import copy import copy
import time import time
import calendar
from ._internal_utils import to_native_string from ._internal_utils import to_native_string
from .compat import cookielib, urlparse, urlunparse, Morsel, MutableMapping from .compat import Morsel, MutableMapping, cookielib, urlparse, urlunparse
try: try:
import threading import threading
@@ -22,7 +20,7 @@ except ImportError:
import dummy_threading as threading import dummy_threading as threading
class MockRequest(object): class MockRequest:
"""Wraps a `requests.Request` to mimic a `urllib2.Request`. """Wraps a `requests.Request` to mimic a `urllib2.Request`.
The code in `cookielib.CookieJar` expects this interface in order to correctly The code in `cookielib.CookieJar` expects this interface in order to correctly
@@ -51,16 +49,22 @@ class MockRequest(object):
def get_full_url(self): def get_full_url(self):
# Only return the response's URL if the user hadn't set the Host # Only return the response's URL if the user hadn't set the Host
# header # header
if not self._r.headers.get('Host'): if not self._r.headers.get("Host"):
return self._r.url return self._r.url
# If they did set it, retrieve it and reconstruct the expected domain # If they did set it, retrieve it and reconstruct the expected domain
host = to_native_string(self._r.headers['Host'], encoding='utf-8') host = to_native_string(self._r.headers["Host"], encoding="utf-8")
parsed = urlparse(self._r.url) parsed = urlparse(self._r.url)
# Reconstruct the URL as we expect it # Reconstruct the URL as we expect it
return urlunparse([ return urlunparse(
parsed.scheme, host, parsed.path, parsed.params, parsed.query, [
parsed.fragment parsed.scheme,
]) host,
parsed.path,
parsed.params,
parsed.query,
parsed.fragment,
]
)
def is_unverifiable(self): def is_unverifiable(self):
return True return True
@@ -73,7 +77,9 @@ class MockRequest(object):
def add_header(self, key, val): def add_header(self, key, val):
"""cookielib has no legitimate use for this method; add it back if you find one.""" """cookielib has no legitimate use for this method; add it back if you find one."""
raise NotImplementedError("Cookie headers should be added with add_unredirected_header()") raise NotImplementedError(
"Cookie headers should be added with add_unredirected_header()"
)
def add_unredirected_header(self, name, value): def add_unredirected_header(self, name, value):
self._new_headers[name] = value self._new_headers[name] = value
@@ -94,7 +100,7 @@ class MockRequest(object):
return self.get_host() return self.get_host()
class MockResponse(object): class MockResponse:
"""Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`. """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.
...what? Basically, expose the parsed HTTP headers from the server response ...what? Basically, expose the parsed HTTP headers from the server response
@@ -122,8 +128,7 @@ def extract_cookies_to_jar(jar, request, response):
:param request: our own requests.Request object :param request: our own requests.Request object
:param response: urllib3.HTTPResponse object :param response: urllib3.HTTPResponse object
""" """
if not (hasattr(response, '_original_response') and if not (hasattr(response, "_original_response") and response._original_response):
response._original_response):
return return
# the _original_response field is the wrapped httplib.HTTPResponse object, # the _original_response field is the wrapped httplib.HTTPResponse object,
req = MockRequest(request) req = MockRequest(request)
@@ -140,7 +145,7 @@ def get_cookie_header(jar, request):
""" """
r = MockRequest(request) r = MockRequest(request)
jar.add_cookie_header(r) jar.add_cookie_header(r)
return r.get_new_headers().get('Cookie') return r.get_new_headers().get("Cookie")
def remove_cookie_by_name(cookiejar, name, domain=None, path=None): def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
@@ -205,7 +210,9 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
""" """
# support client code that unsets cookies by assignment of a None value: # support client code that unsets cookies by assignment of a None value:
if value is None: if value is None:
remove_cookie_by_name(self, name, domain=kwargs.get('domain'), path=kwargs.get('path')) remove_cookie_by_name(
self, name, domain=kwargs.get("domain"), path=kwargs.get("path")
)
return return
if isinstance(value, Morsel): if isinstance(value, Morsel):
@@ -305,16 +312,15 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
""" """
dictionary = {} dictionary = {}
for cookie in iter(self): for cookie in iter(self):
if ( if (domain is None or cookie.domain == domain) and (
(domain is None or cookie.domain == domain) and path is None or cookie.path == path
(path is None or cookie.path == path)
): ):
dictionary[cookie.name] = cookie.value dictionary[cookie.name] = cookie.value
return dictionary return dictionary
def __contains__(self, name): def __contains__(self, name):
try: try:
return super(RequestsCookieJar, self).__contains__(name) return super().__contains__(name)
except CookieConflictError: except CookieConflictError:
return True return True
@@ -341,9 +347,13 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
remove_cookie_by_name(self, name) remove_cookie_by_name(self, name)
def set_cookie(self, cookie, *args, **kwargs): def set_cookie(self, cookie, *args, **kwargs):
if hasattr(cookie.value, 'startswith') and cookie.value.startswith('"') and cookie.value.endswith('"'): if (
cookie.value = cookie.value.replace('\\"', '') hasattr(cookie.value, "startswith")
return super(RequestsCookieJar, self).set_cookie(cookie, *args, **kwargs) and cookie.value.startswith('"')
and cookie.value.endswith('"')
):
cookie.value = cookie.value.replace('\\"', "")
return super().set_cookie(cookie, *args, **kwargs)
def update(self, other): def update(self, other):
"""Updates this jar with cookies from another CookieJar or dict-like""" """Updates this jar with cookies from another CookieJar or dict-like"""
@@ -351,7 +361,7 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
for cookie in other: for cookie in other:
self.set_cookie(copy.copy(cookie)) self.set_cookie(copy.copy(cookie))
else: else:
super(RequestsCookieJar, self).update(other) super().update(other)
def _find(self, name, domain=None, path=None): def _find(self, name, domain=None, path=None):
"""Requests uses this method internally to get cookie values. """Requests uses this method internally to get cookie values.
@@ -371,7 +381,7 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
if path is None or cookie.path == path: if path is None or cookie.path == path:
return cookie.value return cookie.value
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")
def _find_no_duplicates(self, name, domain=None, path=None): def _find_no_duplicates(self, name, domain=None, path=None):
"""Both ``__get_item__`` and ``get`` call this function: it's never """Both ``__get_item__`` and ``get`` call this function: it's never
@@ -390,25 +400,29 @@ class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
if cookie.name == name: if cookie.name == name:
if domain is None or cookie.domain == domain: if domain is None or cookie.domain == domain:
if path is None or cookie.path == path: if path is None or cookie.path == path:
if toReturn is not None: # if there are multiple cookies that meet passed in criteria if toReturn is not None:
raise CookieConflictError('There are multiple cookies with name, %r' % (name)) # if there are multiple cookies that meet passed in criteria
toReturn = cookie.value # we will eventually return this as long as no cookie conflict raise CookieConflictError(
f"There are multiple cookies with name, {name!r}"
)
# we will eventually return this as long as no cookie conflict
toReturn = cookie.value
if toReturn: if toReturn:
return toReturn return toReturn
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")
def __getstate__(self): def __getstate__(self):
"""Unlike a normal CookieJar, this class is pickleable.""" """Unlike a normal CookieJar, this class is pickleable."""
state = self.__dict__.copy() state = self.__dict__.copy()
# remove the unpickleable RLock object # remove the unpickleable RLock object
state.pop('_cookies_lock') state.pop("_cookies_lock")
return state return state
def __setstate__(self, state): def __setstate__(self, state):
"""Unlike a normal CookieJar, this class is pickleable.""" """Unlike a normal CookieJar, this class is pickleable."""
self.__dict__.update(state) self.__dict__.update(state)
if '_cookies_lock' not in self.__dict__: if "_cookies_lock" not in self.__dict__:
self._cookies_lock = threading.RLock() self._cookies_lock = threading.RLock()
def copy(self): def copy(self):
@@ -427,7 +441,7 @@ def _copy_cookie_jar(jar):
if jar is None: if jar is None:
return None return None
if hasattr(jar, 'copy'): if hasattr(jar, "copy"):
# We're dealing with an instance of RequestsCookieJar # We're dealing with an instance of RequestsCookieJar
return jar.copy() return jar.copy()
# We're dealing with a generic CookieJar instance # We're dealing with a generic CookieJar instance
@@ -445,31 +459,32 @@ def create_cookie(name, value, **kwargs):
and sent on every request (this is sometimes called a "supercookie"). and sent on every request (this is sometimes called a "supercookie").
""" """
result = { result = {
'version': 0, "version": 0,
'name': name, "name": name,
'value': value, "value": value,
'port': None, "port": None,
'domain': '', "domain": "",
'path': '/', "path": "/",
'secure': False, "secure": False,
'expires': None, "expires": None,
'discard': True, "discard": True,
'comment': None, "comment": None,
'comment_url': None, "comment_url": None,
'rest': {'HttpOnly': None}, "rest": {"HttpOnly": None},
'rfc2109': False, "rfc2109": False,
} }
badargs = set(kwargs) - set(result) badargs = set(kwargs) - set(result)
if badargs: if badargs:
err = 'create_cookie() got unexpected keyword arguments: %s' raise TypeError(
raise TypeError(err % list(badargs)) f"create_cookie() got unexpected keyword arguments: {list(badargs)}"
)
result.update(kwargs) result.update(kwargs)
result['port_specified'] = bool(result['port']) result["port_specified"] = bool(result["port"])
result['domain_specified'] = bool(result['domain']) result["domain_specified"] = bool(result["domain"])
result['domain_initial_dot'] = result['domain'].startswith('.') result["domain_initial_dot"] = result["domain"].startswith(".")
result['path_specified'] = bool(result['path']) result["path_specified"] = bool(result["path"])
return cookielib.Cookie(**result) return cookielib.Cookie(**result)
@@ -478,30 +493,28 @@ def morsel_to_cookie(morsel):
"""Convert a Morsel object into a Cookie containing the one k/v pair.""" """Convert a Morsel object into a Cookie containing the one k/v pair."""
expires = None expires = None
if morsel['max-age']: if morsel["max-age"]:
try: try:
expires = int(time.time() + int(morsel['max-age'])) expires = int(time.time() + int(morsel["max-age"]))
except ValueError: except ValueError:
raise TypeError('max-age: %s must be integer' % morsel['max-age']) raise TypeError(f"max-age: {morsel['max-age']} must be integer")
elif morsel['expires']: elif morsel["expires"]:
time_template = '%a, %d-%b-%Y %H:%M:%S GMT' time_template = "%a, %d-%b-%Y %H:%M:%S GMT"
expires = calendar.timegm( expires = calendar.timegm(time.strptime(morsel["expires"], time_template))
time.strptime(morsel['expires'], time_template)
)
return create_cookie( return create_cookie(
comment=morsel['comment'], comment=morsel["comment"],
comment_url=bool(morsel['comment']), comment_url=bool(morsel["comment"]),
discard=False, discard=False,
domain=morsel['domain'], domain=morsel["domain"],
expires=expires, expires=expires,
name=morsel.key, name=morsel.key,
path=morsel['path'], path=morsel["path"],
port=None, port=None,
rest={'HttpOnly': morsel['httponly']}, rest={"HttpOnly": morsel["httponly"]},
rfc2109=False, rfc2109=False,
secure=bool(morsel['secure']), secure=bool(morsel["secure"]),
value=morsel.value, value=morsel.value,
version=morsel['version'] or 0, version=morsel["version"] or 0,
) )
@@ -534,11 +547,10 @@ def merge_cookies(cookiejar, cookies):
:rtype: CookieJar :rtype: CookieJar
""" """
if not isinstance(cookiejar, cookielib.CookieJar): if not isinstance(cookiejar, cookielib.CookieJar):
raise ValueError('You can only merge into CookieJar') raise ValueError("You can only merge into CookieJar")
if isinstance(cookies, dict): if isinstance(cookies, dict):
cookiejar = cookiejar_from_dict( cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False)
cookies, cookiejar=cookiejar, overwrite=False)
elif isinstance(cookies, cookielib.CookieJar): elif isinstance(cookies, cookielib.CookieJar):
try: try:
cookiejar.update(cookies) cookiejar.update(cookies)
+5 -7
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
""" """
requests.exceptions requests.exceptions
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
@@ -18,13 +16,12 @@ class RequestException(IOError):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Initialize RequestException with `request` and `response` objects.""" """Initialize RequestException with `request` and `response` objects."""
response = kwargs.pop('response', None) response = kwargs.pop("response", None)
self.response = response self.response = response
self.request = kwargs.pop('request', None) self.request = kwargs.pop("request", None)
if (response is not None and not self.request and if response is not None and not self.request and hasattr(response, "request"):
hasattr(response, 'request')):
self.request = self.response.request self.request = self.response.request
super(RequestException, self).__init__(*args, **kwargs) super().__init__(*args, **kwargs)
class InvalidJSONError(RequestException): class InvalidJSONError(RequestException):
@@ -128,6 +125,7 @@ class RetryError(RequestException):
class UnrewindableBodyError(RequestException): class UnrewindableBodyError(RequestException):
"""Requests encountered an error when trying to rewind a body.""" """Requests encountered an error when trying to rewind a body."""
# Warnings # Warnings
+48 -49
View File
@@ -1,10 +1,9 @@
"""Module containing bug report helper(s).""" """Module containing bug report helper(s)."""
from __future__ import print_function
import json import json
import platform import platform
import sys
import ssl import ssl
import sys
import idna import idna
import urllib3 import urllib3
@@ -28,8 +27,8 @@ except ImportError:
OpenSSL = None OpenSSL = None
cryptography = None cryptography = None
else: else:
import OpenSSL
import cryptography import cryptography
import OpenSSL
def _implementation(): def _implementation():
@@ -45,83 +44,83 @@ def _implementation():
""" """
implementation = platform.python_implementation() implementation = platform.python_implementation()
if implementation == 'CPython': if implementation == "CPython":
implementation_version = platform.python_version() implementation_version = platform.python_version()
elif implementation == 'PyPy': elif implementation == "PyPy":
implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major, implementation_version = "{}.{}.{}".format(
sys.pypy_version_info.minor, sys.pypy_version_info.major,
sys.pypy_version_info.micro) sys.pypy_version_info.minor,
if sys.pypy_version_info.releaselevel != 'final': sys.pypy_version_info.micro,
implementation_version = ''.join([ )
implementation_version, sys.pypy_version_info.releaselevel if sys.pypy_version_info.releaselevel != "final":
]) implementation_version = "".join(
elif implementation == 'Jython': [implementation_version, sys.pypy_version_info.releaselevel]
)
elif implementation == "Jython":
implementation_version = platform.python_version() # Complete Guess implementation_version = platform.python_version() # Complete Guess
elif implementation == 'IronPython': elif implementation == "IronPython":
implementation_version = platform.python_version() # Complete Guess implementation_version = platform.python_version() # Complete Guess
else: else:
implementation_version = 'Unknown' implementation_version = "Unknown"
return {'name': implementation, 'version': implementation_version} return {"name": implementation, "version": implementation_version}
def info(): def info():
"""Generate information for a bug report.""" """Generate information for a bug report."""
try: try:
platform_info = { platform_info = {
'system': platform.system(), "system": platform.system(),
'release': platform.release(), "release": platform.release(),
} }
except IOError: except OSError:
platform_info = { platform_info = {
'system': 'Unknown', "system": "Unknown",
'release': 'Unknown', "release": "Unknown",
} }
implementation_info = _implementation() implementation_info = _implementation()
urllib3_info = {'version': urllib3.__version__} urllib3_info = {"version": urllib3.__version__}
charset_normalizer_info = {'version': None} charset_normalizer_info = {"version": None}
chardet_info = {'version': None} chardet_info = {"version": None}
if charset_normalizer: if charset_normalizer:
charset_normalizer_info = {'version': charset_normalizer.__version__} charset_normalizer_info = {"version": charset_normalizer.__version__}
if chardet: if chardet:
chardet_info = {'version': chardet.__version__} chardet_info = {"version": chardet.__version__}
pyopenssl_info = { pyopenssl_info = {
'version': None, "version": None,
'openssl_version': '', "openssl_version": "",
} }
if OpenSSL: if OpenSSL:
pyopenssl_info = { pyopenssl_info = {
'version': OpenSSL.__version__, "version": OpenSSL.__version__,
'openssl_version': '%x' % OpenSSL.SSL.OPENSSL_VERSION_NUMBER, "openssl_version": f"{OpenSSL.SSL.OPENSSL_VERSION_NUMBER:x}",
} }
cryptography_info = { cryptography_info = {
'version': getattr(cryptography, '__version__', ''), "version": getattr(cryptography, "__version__", ""),
} }
idna_info = { idna_info = {
'version': getattr(idna, '__version__', ''), "version": getattr(idna, "__version__", ""),
} }
system_ssl = ssl.OPENSSL_VERSION_NUMBER system_ssl = ssl.OPENSSL_VERSION_NUMBER
system_ssl_info = { system_ssl_info = {"version": f"{system_ssl:x}" if system_ssl is not None else ""}
'version': '%x' % system_ssl if system_ssl is not None else ''
}
return { return {
'platform': platform_info, "platform": platform_info,
'implementation': implementation_info, "implementation": implementation_info,
'system_ssl': system_ssl_info, "system_ssl": system_ssl_info,
'using_pyopenssl': pyopenssl is not None, "using_pyopenssl": pyopenssl is not None,
'using_charset_normalizer': chardet is None, "using_charset_normalizer": chardet is None,
'pyOpenSSL': pyopenssl_info, "pyOpenSSL": pyopenssl_info,
'urllib3': urllib3_info, "urllib3": urllib3_info,
'chardet': chardet_info, "chardet": chardet_info,
'charset_normalizer': charset_normalizer_info, "charset_normalizer": charset_normalizer_info,
'cryptography': cryptography_info, "cryptography": cryptography_info,
'idna': idna_info, "idna": idna_info,
'requests': { "requests": {
'version': requests_version, "version": requests_version,
}, },
} }
@@ -131,5 +130,5 @@ def main():
print(json.dumps(info(), sort_keys=True, indent=2)) print(json.dumps(info(), sort_keys=True, indent=2))
if __name__ == '__main__': if __name__ == "__main__":
main() main()
+3 -4
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
""" """
requests.hooks requests.hooks
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
@@ -11,12 +9,13 @@ Available hooks:
``response``: ``response``:
The response generated from a Request. The response generated from a Request.
""" """
HOOKS = ['response'] HOOKS = ["response"]
def default_hooks(): def default_hooks():
return {event: [] for event in HOOKS} return {event: [] for event in HOOKS}
# TODO: response is the only one # TODO: response is the only one
@@ -25,7 +24,7 @@ def dispatch_hook(key, hooks, hook_data, **kwargs):
hooks = hooks or {} hooks = hooks or {}
hooks = hooks.get(key) hooks = hooks.get(key)
if hooks: if hooks:
if hasattr(hooks, '__call__'): if hasattr(hooks, "__call__"):
hooks = [hooks] hooks = [hooks]
for hook in hooks: for hook in hooks:
_hook_data = hook(hook_data, **kwargs) _hook_data = hook(hook_data, **kwargs)
+193 -125
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
""" """
requests.models requests.models
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
@@ -12,11 +10,9 @@ import datetime
# Import encoding now, to avoid implicit import later. # Import encoding now, to avoid implicit import later.
# Implicit import within threads may cause LookupError when standard library is in a ZIP, # Implicit import within threads may cause LookupError when standard library is in a ZIP,
# such as in Embedded Python. See https://github.com/psf/requests/issues/3578. # such as in Embedded Python. See https://github.com/psf/requests/issues/3578.
import encodings.idna import encodings.idna # noqa: F401
from io import UnsupportedOperation
from urllib3.fields import RequestField
from urllib3.filepost import encode_multipart_formdata
from urllib3.util import parse_url
from urllib3.exceptions import ( from urllib3.exceptions import (
DecodeError, DecodeError,
LocationParseError, LocationParseError,
@@ -24,37 +20,58 @@ from urllib3.exceptions import (
ReadTimeoutError, ReadTimeoutError,
SSLError, SSLError,
) )
from urllib3.fields import RequestField
from urllib3.filepost import encode_multipart_formdata
from urllib3.util import parse_url
from io import UnsupportedOperation
from .hooks import default_hooks
from .structures import CaseInsensitiveDict
from .auth import HTTPBasicAuth
from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar
from .exceptions import (
HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError,
ContentDecodingError, ConnectionError, StreamConsumedError,
InvalidJSONError)
from .exceptions import JSONDecodeError as RequestsJSONDecodeError
from .exceptions import SSLError as RequestsSSLError
from ._internal_utils import to_native_string, unicode_is_ascii from ._internal_utils import to_native_string, unicode_is_ascii
from .utils import ( from .auth import HTTPBasicAuth
guess_filename, get_auth_from_url, requote_uri,
stream_decode_response_unicode, to_key_val_list, parse_header_links,
iter_slices, guess_json_utf, super_len, check_header_validity)
from .compat import ( from .compat import (
Callable, Mapping, Callable,
cookielib, urlunparse, urlsplit, urlencode, JSONDecodeError,
chardet, builtin_str, basestring, JSONDecodeError) Mapping,
basestring,
builtin_str,
chardet,
cookielib,
)
from .compat import json as complexjson from .compat import json as complexjson
from .compat import urlencode, urlsplit, urlunparse
from .cookies import _copy_cookie_jar, cookiejar_from_dict, get_cookie_header
from .exceptions import (
ChunkedEncodingError,
ConnectionError,
ContentDecodingError,
HTTPError,
InvalidJSONError,
InvalidURL,
)
from .exceptions import JSONDecodeError as RequestsJSONDecodeError
from .exceptions import MissingSchema
from .exceptions import SSLError as RequestsSSLError
from .exceptions import StreamConsumedError
from .hooks import default_hooks
from .status_codes import codes from .status_codes import codes
from .structures import CaseInsensitiveDict
from .utils import (
check_header_validity,
get_auth_from_url,
guess_filename,
guess_json_utf,
iter_slices,
parse_header_links,
requote_uri,
stream_decode_response_unicode,
super_len,
to_key_val_list,
)
#: The set of HTTP status codes that indicate an automatically #: The set of HTTP status codes that indicate an automatically
#: processable redirect. #: processable redirect.
REDIRECT_STATI = ( REDIRECT_STATI = (
codes.moved, # 301 codes.moved, # 301
codes.found, # 302 codes.found, # 302
codes.other, # 303 codes.other, # 303
codes.temporary_redirect, # 307 codes.temporary_redirect, # 307
codes.permanent_redirect, # 308 codes.permanent_redirect, # 308
) )
@@ -64,7 +81,7 @@ CONTENT_CHUNK_SIZE = 10 * 1024
ITER_CHUNK_SIZE = 512 ITER_CHUNK_SIZE = 512
class RequestEncodingMixin(object): class RequestEncodingMixin:
@property @property
def path_url(self): def path_url(self):
"""Build the path URL to use.""" """Build the path URL to use."""
@@ -75,16 +92,16 @@ class RequestEncodingMixin(object):
path = p.path path = p.path
if not path: if not path:
path = '/' path = "/"
url.append(path) url.append(path)
query = p.query query = p.query
if query: if query:
url.append('?') url.append("?")
url.append(query) url.append(query)
return ''.join(url) return "".join(url)
@staticmethod @staticmethod
def _encode_params(data): def _encode_params(data):
@@ -97,18 +114,21 @@ class RequestEncodingMixin(object):
if isinstance(data, (str, bytes)): if isinstance(data, (str, bytes)):
return data return data
elif hasattr(data, 'read'): elif hasattr(data, "read"):
return data return data
elif hasattr(data, '__iter__'): elif hasattr(data, "__iter__"):
result = [] result = []
for k, vs in to_key_val_list(data): for k, vs in to_key_val_list(data):
if isinstance(vs, basestring) or not hasattr(vs, '__iter__'): if isinstance(vs, basestring) or not hasattr(vs, "__iter__"):
vs = [vs] vs = [vs]
for v in vs: for v in vs:
if v is not None: if v is not None:
result.append( result.append(
(k.encode('utf-8') if isinstance(k, str) else k, (
v.encode('utf-8') if isinstance(v, str) else v)) k.encode("utf-8") if isinstance(k, str) else k,
v.encode("utf-8") if isinstance(v, str) else v,
)
)
return urlencode(result, doseq=True) return urlencode(result, doseq=True)
else: else:
return data return data
@@ -123,7 +143,7 @@ class RequestEncodingMixin(object):
The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype) The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype)
or 4-tuples (filename, fileobj, contentype, custom_headers). or 4-tuples (filename, fileobj, contentype, custom_headers).
""" """
if (not files): if not files:
raise ValueError("Files must be provided.") raise ValueError("Files must be provided.")
elif isinstance(data, basestring): elif isinstance(data, basestring):
raise ValueError("Data must not be a string.") raise ValueError("Data must not be a string.")
@@ -133,7 +153,7 @@ class RequestEncodingMixin(object):
files = to_key_val_list(files or {}) files = to_key_val_list(files or {})
for field, val in fields: for field, val in fields:
if isinstance(val, basestring) or not hasattr(val, '__iter__'): if isinstance(val, basestring) or not hasattr(val, "__iter__"):
val = [val] val = [val]
for v in val: for v in val:
if v is not None: if v is not None:
@@ -142,8 +162,13 @@ class RequestEncodingMixin(object):
v = str(v) v = str(v)
new_fields.append( new_fields.append(
(field.decode('utf-8') if isinstance(field, bytes) else field, (
v.encode('utf-8') if isinstance(v, str) else v)) field.decode("utf-8")
if isinstance(field, bytes)
else field,
v.encode("utf-8") if isinstance(v, str) else v,
)
)
for (k, v) in files: for (k, v) in files:
# support for explicit filename # support for explicit filename
@@ -162,7 +187,7 @@ class RequestEncodingMixin(object):
if isinstance(fp, (str, bytes, bytearray)): if isinstance(fp, (str, bytes, bytearray)):
fdata = fp fdata = fp
elif hasattr(fp, 'read'): elif hasattr(fp, "read"):
fdata = fp.read() fdata = fp.read()
elif fp is None: elif fp is None:
continue continue
@@ -178,16 +203,16 @@ class RequestEncodingMixin(object):
return body, content_type return body, content_type
class RequestHooksMixin(object): class RequestHooksMixin:
def register_hook(self, event, hook): def register_hook(self, event, hook):
"""Properly register a hook.""" """Properly register a hook."""
if event not in self.hooks: if event not in self.hooks:
raise ValueError('Unsupported event specified, with event name "%s"' % (event)) raise ValueError(f'Unsupported event specified, with event name "{event}"')
if isinstance(hook, Callable): if isinstance(hook, Callable):
self.hooks[event].append(hook) self.hooks[event].append(hook)
elif hasattr(hook, '__iter__'): elif hasattr(hook, "__iter__"):
self.hooks[event].extend(h for h in hook if isinstance(h, Callable)) self.hooks[event].extend(h for h in hook if isinstance(h, Callable))
def deregister_hook(self, event, hook): def deregister_hook(self, event, hook):
@@ -230,9 +255,19 @@ class Request(RequestHooksMixin):
<PreparedRequest [GET]> <PreparedRequest [GET]>
""" """
def __init__(self, def __init__(
method=None, url=None, headers=None, files=None, data=None, self,
params=None, auth=None, cookies=None, hooks=None, json=None): method=None,
url=None,
headers=None,
files=None,
data=None,
params=None,
auth=None,
cookies=None,
hooks=None,
json=None,
):
# Default empty dicts for dict params. # Default empty dicts for dict params.
data = [] if data is None else data data = [] if data is None else data
@@ -256,7 +291,7 @@ class Request(RequestHooksMixin):
self.cookies = cookies self.cookies = cookies
def __repr__(self): def __repr__(self):
return '<Request [%s]>' % (self.method) return f"<Request [{self.method}]>"
def prepare(self): def prepare(self):
"""Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it.""" """Constructs a :class:`PreparedRequest <PreparedRequest>` for transmission and returns it."""
@@ -314,9 +349,19 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
#: integer denoting starting position of a readable file-like body. #: integer denoting starting position of a readable file-like body.
self._body_position = None self._body_position = None
def prepare(self, def prepare(
method=None, url=None, headers=None, files=None, data=None, self,
params=None, auth=None, cookies=None, hooks=None, json=None): method=None,
url=None,
headers=None,
files=None,
data=None,
params=None,
auth=None,
cookies=None,
hooks=None,
json=None,
):
"""Prepares the entire request with the given parameters.""" """Prepares the entire request with the given parameters."""
self.prepare_method(method) self.prepare_method(method)
@@ -333,7 +378,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
self.prepare_hooks(hooks) self.prepare_hooks(hooks)
def __repr__(self): def __repr__(self):
return '<PreparedRequest [%s]>' % (self.method) return f"<PreparedRequest [{self.method}]>"
def copy(self): def copy(self):
p = PreparedRequest() p = PreparedRequest()
@@ -357,7 +402,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
import idna import idna
try: try:
host = idna.encode(host, uts46=True).decode('utf-8') host = idna.encode(host, uts46=True).decode("utf-8")
except idna.IDNAError: except idna.IDNAError:
raise UnicodeError raise UnicodeError
return host return host
@@ -370,7 +415,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
#: on python 3.x. #: on python 3.x.
#: https://github.com/psf/requests/pull/2238 #: https://github.com/psf/requests/pull/2238
if isinstance(url, bytes): if isinstance(url, bytes):
url = url.decode('utf8') url = url.decode("utf8")
else: else:
url = str(url) url = str(url)
@@ -380,7 +425,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
# Don't do any URL preparation for non-HTTP schemes like `mailto`, # Don't do any URL preparation for non-HTTP schemes like `mailto`,
# `data` etc to work around exceptions from `url_parse`, which # `data` etc to work around exceptions from `url_parse`, which
# handles RFC 3986 only. # handles RFC 3986 only.
if ':' in url and not url.lower().startswith('http'): if ":" in url and not url.lower().startswith("http"):
self.url = url self.url = url
return return
@@ -391,13 +436,13 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
raise InvalidURL(*e.args) raise InvalidURL(*e.args)
if not scheme: if not scheme:
error = ("Invalid URL {0!r}: No scheme supplied. Perhaps you meant http://{0}?") raise MissingSchema(
error = error.format(to_native_string(url, 'utf8')) f"Invalid URL {url!r}: No scheme supplied. "
f"Perhaps you meant http://{url}?"
raise MissingSchema(error) )
if not host: if not host:
raise InvalidURL("Invalid URL %r: No host supplied" % url) raise InvalidURL(f"Invalid URL {url!r}: No host supplied")
# In general, we want to try IDNA encoding the hostname if the string contains # In general, we want to try IDNA encoding the hostname if the string contains
# non-ASCII characters. This allows users to automatically get the correct IDNA # non-ASCII characters. This allows users to automatically get the correct IDNA
@@ -407,21 +452,21 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
try: try:
host = self._get_idna_encoded_host(host) host = self._get_idna_encoded_host(host)
except UnicodeError: except UnicodeError:
raise InvalidURL('URL has an invalid label.') raise InvalidURL("URL has an invalid label.")
elif host.startswith((u'*', u'.')): elif host.startswith(("*", ".")):
raise InvalidURL('URL has an invalid label.') raise InvalidURL("URL has an invalid label.")
# Carefully reconstruct the network location # Carefully reconstruct the network location
netloc = auth or '' netloc = auth or ""
if netloc: if netloc:
netloc += '@' netloc += "@"
netloc += host netloc += host
if port: if port:
netloc += ':' + str(port) netloc += f":{port}"
# Bare domains aren't valid URLs. # Bare domains aren't valid URLs.
if not path: if not path:
path = '/' path = "/"
if isinstance(params, (str, bytes)): if isinstance(params, (str, bytes)):
params = to_native_string(params) params = to_native_string(params)
@@ -429,7 +474,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
enc_params = self._encode_params(params) enc_params = self._encode_params(params)
if enc_params: if enc_params:
if query: if query:
query = '%s&%s' % (query, enc_params) query = f"{query}&{enc_params}"
else: else:
query = enc_params query = enc_params
@@ -460,7 +505,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
if not data and json is not None: if not data and json is not None:
# urllib3 requires a bytes-like body. Python 2's json.dumps # urllib3 requires a bytes-like body. Python 2's json.dumps
# provides this natively, but Python 3 gives a Unicode string. # provides this natively, but Python 3 gives a Unicode string.
content_type = 'application/json' content_type = "application/json"
try: try:
body = complexjson.dumps(json, allow_nan=False) body = complexjson.dumps(json, allow_nan=False)
@@ -468,12 +513,14 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
raise InvalidJSONError(ve, request=self) raise InvalidJSONError(ve, request=self)
if not isinstance(body, bytes): if not isinstance(body, bytes):
body = body.encode('utf-8') body = body.encode("utf-8")
is_stream = all([ is_stream = all(
hasattr(data, '__iter__'), [
not isinstance(data, (basestring, list, tuple, Mapping)) hasattr(data, "__iter__"),
]) not isinstance(data, (basestring, list, tuple, Mapping)),
]
)
if is_stream: if is_stream:
try: try:
@@ -483,24 +530,26 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
body = data body = data
if getattr(body, 'tell', None) is not None: if getattr(body, "tell", None) is not None:
# Record the current file position before reading. # Record the current file position before reading.
# This will allow us to rewind a file in the event # This will allow us to rewind a file in the event
# of a redirect. # of a redirect.
try: try:
self._body_position = body.tell() self._body_position = body.tell()
except (IOError, OSError): except OSError:
# This differentiates from None, allowing us to catch # This differentiates from None, allowing us to catch
# a failed `tell()` later when trying to rewind the body # a failed `tell()` later when trying to rewind the body
self._body_position = object() self._body_position = object()
if files: if files:
raise NotImplementedError('Streamed bodies and files are mutually exclusive.') raise NotImplementedError(
"Streamed bodies and files are mutually exclusive."
)
if length: if length:
self.headers['Content-Length'] = builtin_str(length) self.headers["Content-Length"] = builtin_str(length)
else: else:
self.headers['Transfer-Encoding'] = 'chunked' self.headers["Transfer-Encoding"] = "chunked"
else: else:
# Multi-part file uploads. # Multi-part file uploads.
if files: if files:
@@ -508,16 +557,16 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
else: else:
if data: if data:
body = self._encode_params(data) body = self._encode_params(data)
if isinstance(data, basestring) or hasattr(data, 'read'): if isinstance(data, basestring) or hasattr(data, "read"):
content_type = None content_type = None
else: else:
content_type = 'application/x-www-form-urlencoded' content_type = "application/x-www-form-urlencoded"
self.prepare_content_length(body) self.prepare_content_length(body)
# Add content-type if it wasn't explicitly provided. # Add content-type if it wasn't explicitly provided.
if content_type and ('content-type' not in self.headers): if content_type and ("content-type" not in self.headers):
self.headers['Content-Type'] = content_type self.headers["Content-Type"] = content_type
self.body = body self.body = body
@@ -528,13 +577,16 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
if length: if length:
# If length exists, set it. Otherwise, we fallback # If length exists, set it. Otherwise, we fallback
# to Transfer-Encoding: chunked. # to Transfer-Encoding: chunked.
self.headers['Content-Length'] = builtin_str(length) self.headers["Content-Length"] = builtin_str(length)
elif self.method not in ('GET', 'HEAD') and self.headers.get('Content-Length') is None: elif (
self.method not in ("GET", "HEAD")
and self.headers.get("Content-Length") is None
):
# Set Content-Length to 0 for methods that can have a body # Set Content-Length to 0 for methods that can have a body
# but don't provide one. (i.e. not GET or HEAD) # but don't provide one. (i.e. not GET or HEAD)
self.headers['Content-Length'] = '0' self.headers["Content-Length"] = "0"
def prepare_auth(self, auth, url=''): def prepare_auth(self, auth, url=""):
"""Prepares the given HTTP auth data.""" """Prepares the given HTTP auth data."""
# If no Auth is explicitly provided, extract it from the URL first. # If no Auth is explicitly provided, extract it from the URL first.
@@ -574,7 +626,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
cookie_header = get_cookie_header(self._cookies, self) cookie_header = get_cookie_header(self._cookies, self)
if cookie_header is not None: if cookie_header is not None:
self.headers['Cookie'] = cookie_header self.headers["Cookie"] = cookie_header
def prepare_hooks(self, hooks): def prepare_hooks(self, hooks):
"""Prepares the given hooks.""" """Prepares the given hooks."""
@@ -586,14 +638,22 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
self.register_hook(event, hooks[event]) self.register_hook(event, hooks[event])
class Response(object): class Response:
"""The :class:`Response <Response>` object, which contains a """The :class:`Response <Response>` object, which contains a
server's response to an HTTP request. server's response to an HTTP request.
""" """
__attrs__ = [ __attrs__ = [
'_content', 'status_code', 'headers', 'url', 'history', "_content",
'encoding', 'reason', 'cookies', 'elapsed', 'request' "status_code",
"headers",
"url",
"history",
"encoding",
"reason",
"cookies",
"elapsed",
"request",
] ]
def __init__(self): def __init__(self):
@@ -662,11 +722,11 @@ class Response(object):
setattr(self, name, value) setattr(self, name, value)
# pickled objects do not have .raw # pickled objects do not have .raw
setattr(self, '_content_consumed', True) setattr(self, "_content_consumed", True)
setattr(self, 'raw', None) setattr(self, "raw", None)
def __repr__(self): def __repr__(self):
return '<Response [%s]>' % (self.status_code) return f"<Response [{self.status_code}]>"
def __bool__(self): def __bool__(self):
"""Returns True if :attr:`status_code` is less than 400. """Returns True if :attr:`status_code` is less than 400.
@@ -712,12 +772,15 @@ class Response(object):
"""True if this Response is a well-formed HTTP redirect that could have """True if this Response is a well-formed HTTP redirect that could have
been processed automatically (by :meth:`Session.resolve_redirects`). been processed automatically (by :meth:`Session.resolve_redirects`).
""" """
return ('location' in self.headers and self.status_code in REDIRECT_STATI) return "location" in self.headers and self.status_code in REDIRECT_STATI
@property @property
def is_permanent_redirect(self): def is_permanent_redirect(self):
"""True if this Response one of the permanent versions of redirect.""" """True if this Response one of the permanent versions of redirect."""
return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect)) return "location" in self.headers and self.status_code in (
codes.moved_permanently,
codes.permanent_redirect,
)
@property @property
def next(self): def next(self):
@@ -727,7 +790,7 @@ class Response(object):
@property @property
def apparent_encoding(self): def apparent_encoding(self):
"""The apparent encoding, provided by the charset_normalizer or chardet libraries.""" """The apparent encoding, provided by the charset_normalizer or chardet libraries."""
return chardet.detect(self.content)['encoding'] return chardet.detect(self.content)["encoding"]
def iter_content(self, chunk_size=1, decode_unicode=False): def iter_content(self, chunk_size=1, decode_unicode=False):
"""Iterates over the response data. When stream=True is set on the """Iterates over the response data. When stream=True is set on the
@@ -748,7 +811,7 @@ class Response(object):
def generate(): def generate():
# Special case for urllib3. # Special case for urllib3.
if hasattr(self.raw, 'stream'): if hasattr(self.raw, "stream"):
try: try:
for chunk in self.raw.stream(chunk_size, decode_content=True): for chunk in self.raw.stream(chunk_size, decode_content=True):
yield chunk yield chunk
@@ -773,7 +836,9 @@ class Response(object):
if self._content_consumed and isinstance(self._content, bool): if self._content_consumed and isinstance(self._content, bool):
raise StreamConsumedError() raise StreamConsumedError()
elif chunk_size is not None and not isinstance(chunk_size, int): elif chunk_size is not None and not isinstance(chunk_size, int):
raise TypeError("chunk_size must be an int, it is instead a %s." % type(chunk_size)) raise TypeError(
f"chunk_size must be an int, it is instead a {type(chunk_size)}."
)
# simulate reading small chunks of the content # simulate reading small chunks of the content
reused_chunks = iter_slices(self._content, chunk_size) reused_chunks = iter_slices(self._content, chunk_size)
@@ -786,7 +851,9 @@ class Response(object):
return chunks return chunks
def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=False, delimiter=None): def iter_lines(
self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=False, delimiter=None
):
"""Iterates over the response data, one line at a time. When """Iterates over the response data, one line at a time. When
stream=True is set on the request, this avoids reading the stream=True is set on the request, this avoids reading the
content at once into memory for large responses. content at once into memory for large responses.
@@ -796,7 +863,9 @@ class Response(object):
pending = None pending = None
for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode): for chunk in self.iter_content(
chunk_size=chunk_size, decode_unicode=decode_unicode
):
if pending is not None: if pending is not None:
chunk = pending + chunk chunk = pending + chunk
@@ -811,8 +880,7 @@ class Response(object):
else: else:
pending = None pending = None
for line in lines: yield from lines
yield line
if pending is not None: if pending is not None:
yield pending yield pending
@@ -824,13 +892,12 @@ class Response(object):
if self._content is False: if self._content is False:
# Read the contents. # Read the contents.
if self._content_consumed: if self._content_consumed:
raise RuntimeError( raise RuntimeError("The content for this response was already consumed")
'The content for this response was already consumed')
if self.status_code == 0 or self.raw is None: if self.status_code == 0 or self.raw is None:
self._content = None self._content = None
else: else:
self._content = b''.join(self.iter_content(CONTENT_CHUNK_SIZE)) or b'' self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b""
self._content_consumed = True self._content_consumed = True
# don't need to release the connection; that's been handled by urllib3 # don't need to release the connection; that's been handled by urllib3
@@ -855,7 +922,7 @@ class Response(object):
encoding = self.encoding encoding = self.encoding
if not self.content: if not self.content:
return str('') return ""
# Fallback to auto-detected encoding. # Fallback to auto-detected encoding.
if self.encoding is None: if self.encoding is None:
@@ -863,7 +930,7 @@ class Response(object):
# Decode unicode from given encoding. # Decode unicode from given encoding.
try: try:
content = str(self.content, encoding, errors='replace') content = str(self.content, encoding, errors="replace")
except (LookupError, TypeError): except (LookupError, TypeError):
# A LookupError is raised if the encoding was not found which could # A LookupError is raised if the encoding was not found which could
# indicate a misspelling or similar mistake. # indicate a misspelling or similar mistake.
@@ -871,7 +938,7 @@ class Response(object):
# A TypeError can be raised if encoding is None # A TypeError can be raised if encoding is None
# #
# So we try blindly encoding. # So we try blindly encoding.
content = str(self.content, errors='replace') content = str(self.content, errors="replace")
return content return content
@@ -891,9 +958,7 @@ class Response(object):
encoding = guess_json_utf(self.content) encoding = guess_json_utf(self.content)
if encoding is not None: if encoding is not None:
try: try:
return complexjson.loads( return complexjson.loads(self.content.decode(encoding), **kwargs)
self.content.decode(encoding), **kwargs
)
except UnicodeDecodeError: except UnicodeDecodeError:
# Wrong UTF codec detected; usually because it's not UTF-8 # Wrong UTF codec detected; usually because it's not UTF-8
# but some other 8-bit codec. This is an RFC violation, # but some other 8-bit codec. This is an RFC violation,
@@ -914,41 +979,44 @@ class Response(object):
def links(self): def links(self):
"""Returns the parsed header links of the response, if any.""" """Returns the parsed header links of the response, if any."""
header = self.headers.get('link') header = self.headers.get("link")
# l = MultiDict() resolved_links = {}
l = {}
if header: if header:
links = parse_header_links(header) links = parse_header_links(header)
for link in links: for link in links:
key = link.get('rel') or link.get('url') key = link.get("rel") or link.get("url")
l[key] = link resolved_links[key] = link
return l return resolved_links
def raise_for_status(self): def raise_for_status(self):
"""Raises :class:`HTTPError`, if one occurred.""" """Raises :class:`HTTPError`, if one occurred."""
http_error_msg = '' http_error_msg = ""
if isinstance(self.reason, bytes): if isinstance(self.reason, bytes):
# We attempt to decode utf-8 first because some servers # We attempt to decode utf-8 first because some servers
# choose to localize their reason strings. If the string # choose to localize their reason strings. If the string
# isn't utf-8, we fall back to iso-8859-1 for all other # isn't utf-8, we fall back to iso-8859-1 for all other
# encodings. (See PR #3538) # encodings. (See PR #3538)
try: try:
reason = self.reason.decode('utf-8') reason = self.reason.decode("utf-8")
except UnicodeDecodeError: except UnicodeDecodeError:
reason = self.reason.decode('iso-8859-1') reason = self.reason.decode("iso-8859-1")
else: else:
reason = self.reason reason = self.reason
if 400 <= self.status_code < 500: if 400 <= self.status_code < 500:
http_error_msg = u'%s Client Error: %s for url: %s' % (self.status_code, reason, self.url) http_error_msg = (
f"{self.status_code} Client Error: {reason} for url: {self.url}"
)
elif 500 <= self.status_code < 600: elif 500 <= self.status_code < 600:
http_error_msg = u'%s Server Error: %s for url: %s' % (self.status_code, reason, self.url) http_error_msg = (
f"{self.status_code} Server Error: {reason} for url: {self.url}"
)
if http_error_msg: if http_error_msg:
raise HTTPError(http_error_msg, response=self) raise HTTPError(http_error_msg, response=self)
@@ -962,6 +1030,6 @@ class Response(object):
if not self._content_consumed: if not self._content_consumed:
self.raw.close() self.raw.close()
release_conn = getattr(self.raw, 'release_conn', None) release_conn = getattr(self.raw, "release_conn", None)
if release_conn is not None: if release_conn is not None:
release_conn() release_conn()
+9 -7
View File
@@ -3,24 +3,26 @@ import sys
try: try:
import chardet import chardet
except ImportError: except ImportError:
import charset_normalizer as chardet
import warnings import warnings
warnings.filterwarnings('ignore', 'Trying to detect', module='charset_normalizer') import charset_normalizer as chardet
warnings.filterwarnings("ignore", "Trying to detect", module="charset_normalizer")
# This code exists for backwards compatibility reasons. # This code exists for backwards compatibility reasons.
# I don't like it either. Just look the other way. :) # I don't like it either. Just look the other way. :)
for package in ('urllib3', 'idna'): for package in ("urllib3", "idna"):
locals()[package] = __import__(package) locals()[package] = __import__(package)
# This traversal is apparently necessary such that the identities are # This traversal is apparently necessary such that the identities are
# preserved (requests.packages.urllib3.* is urllib3.*) # preserved (requests.packages.urllib3.* is urllib3.*)
for mod in list(sys.modules): for mod in list(sys.modules):
if mod == package or mod.startswith(package + '.'): if mod == package or mod.startswith(f"{package}."):
sys.modules['requests.packages.' + mod] = sys.modules[mod] sys.modules[f"requests.packages.{mod}"] = sys.modules[mod]
target = chardet.__name__ target = chardet.__name__
for mod in list(sys.modules): for mod in list(sys.modules):
if mod == target or mod.startswith(target + '.'): if mod == target or mod.startswith(f"{target}."):
sys.modules['requests.packages.' + target.replace(target, 'chardet')] = sys.modules[mod] target = target.replace(target, "chardet")
sys.modules[f"requests.packages.{target}"] = sys.modules[mod]
# Kinda cool, though, right? # Kinda cool, though, right?
+162 -105
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
""" """
requests.sessions requests.sessions
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
@@ -10,35 +8,51 @@ requests (cookies, auth, proxies).
import os import os
import sys import sys
import time import time
from datetime import timedelta
from collections import OrderedDict from collections import OrderedDict
from datetime import timedelta
from .auth import _basic_auth_str
from .compat import cookielib, urljoin, urlparse, Mapping
from .cookies import (
cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies)
from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT
from .hooks import default_hooks, dispatch_hook
from ._internal_utils import to_native_string from ._internal_utils import to_native_string
from .utils import to_key_val_list, default_headers, DEFAULT_PORTS
from .exceptions import (
TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError)
from .structures import CaseInsensitiveDict
from .adapters import HTTPAdapter from .adapters import HTTPAdapter
from .auth import _basic_auth_str
from .utils import ( from .compat import Mapping, cookielib, urljoin, urlparse
requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies, from .cookies import (
get_auth_from_url, rewind_body, resolve_proxies RequestsCookieJar,
cookiejar_from_dict,
extract_cookies_to_jar,
merge_cookies,
) )
from .exceptions import (
from .status_codes import codes ChunkedEncodingError,
ContentDecodingError,
InvalidSchema,
TooManyRedirects,
)
from .hooks import default_hooks, dispatch_hook
# formerly defined here, reexposed here for backward compatibility # formerly defined here, reexposed here for backward compatibility
from .models import REDIRECT_STATI from .models import ( # noqa: F401
DEFAULT_REDIRECT_LIMIT,
REDIRECT_STATI,
PreparedRequest,
Request,
)
from .status_codes import codes
from .structures import CaseInsensitiveDict
from .utils import ( # noqa: F401
DEFAULT_PORTS,
default_headers,
get_auth_from_url,
get_environ_proxies,
get_netrc_auth,
requote_uri,
resolve_proxies,
rewind_body,
should_bypass_proxies,
to_key_val_list,
)
# Preferred clock, based on which one is more accurate on a given system. # Preferred clock, based on which one is more accurate on a given system.
if sys.platform == 'win32': if sys.platform == "win32":
preferred_clock = time.perf_counter preferred_clock = time.perf_counter
else: else:
preferred_clock = time.time preferred_clock = time.time
@@ -58,8 +72,7 @@ def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
# Bypass if not a dictionary (e.g. verify) # Bypass if not a dictionary (e.g. verify)
if not ( if not (
isinstance(session_setting, Mapping) and isinstance(session_setting, Mapping) and isinstance(request_setting, Mapping)
isinstance(request_setting, Mapping)
): ):
return request_setting return request_setting
@@ -81,17 +94,16 @@ def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
This is necessary because when request_hooks == {'response': []}, the This is necessary because when request_hooks == {'response': []}, the
merge breaks Session hooks entirely. merge breaks Session hooks entirely.
""" """
if session_hooks is None or session_hooks.get('response') == []: if session_hooks is None or session_hooks.get("response") == []:
return request_hooks return request_hooks
if request_hooks is None or request_hooks.get('response') == []: if request_hooks is None or request_hooks.get("response") == []:
return session_hooks return session_hooks
return merge_setting(request_hooks, session_hooks, dict_class) return merge_setting(request_hooks, session_hooks, dict_class)
class SessionRedirectMixin(object): class SessionRedirectMixin:
def get_redirect_target(self, resp): def get_redirect_target(self, resp):
"""Receives a Response. Returns a redirect URI or ``None``""" """Receives a Response. Returns a redirect URI or ``None``"""
# Due to the nature of how requests processes redirects this method will # Due to the nature of how requests processes redirects this method will
@@ -101,15 +113,15 @@ class SessionRedirectMixin(object):
# to cache the redirect location onto the response object as a private # to cache the redirect location onto the response object as a private
# attribute. # attribute.
if resp.is_redirect: if resp.is_redirect:
location = resp.headers['location'] location = resp.headers["location"]
# Currently the underlying http module on py3 decode headers # Currently the underlying http module on py3 decode headers
# in latin1, but empirical evidence suggests that latin1 is very # in latin1, but empirical evidence suggests that latin1 is very
# rarely used with non-ASCII characters in HTTP headers. # rarely used with non-ASCII characters in HTTP headers.
# It is more likely to get UTF8 header rather than latin1. # It is more likely to get UTF8 header rather than latin1.
# This causes incorrect handling of UTF8 encoded location headers. # This causes incorrect handling of UTF8 encoded location headers.
# To solve this, we re-encode the location in latin1. # To solve this, we re-encode the location in latin1.
location = location.encode('latin1') location = location.encode("latin1")
return to_native_string(location, 'utf8') return to_native_string(location, "utf8")
return None return None
def should_strip_auth(self, old_url, new_url): def should_strip_auth(self, old_url, new_url):
@@ -122,23 +134,40 @@ class SessionRedirectMixin(object):
# ports. This isn't specified by RFC 7235, but is kept to avoid # ports. This isn't specified by RFC 7235, but is kept to avoid
# breaking backwards compatibility with older versions of requests # breaking backwards compatibility with older versions of requests
# that allowed any redirects on the same host. # that allowed any redirects on the same host.
if (old_parsed.scheme == 'http' and old_parsed.port in (80, None) if (
and new_parsed.scheme == 'https' and new_parsed.port in (443, None)): old_parsed.scheme == "http"
and old_parsed.port in (80, None)
and new_parsed.scheme == "https"
and new_parsed.port in (443, None)
):
return False return False
# Handle default port usage corresponding to scheme. # Handle default port usage corresponding to scheme.
changed_port = old_parsed.port != new_parsed.port changed_port = old_parsed.port != new_parsed.port
changed_scheme = old_parsed.scheme != new_parsed.scheme changed_scheme = old_parsed.scheme != new_parsed.scheme
default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None) default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None)
if (not changed_scheme and old_parsed.port in default_port if (
and new_parsed.port in default_port): not changed_scheme
and old_parsed.port in default_port
and new_parsed.port in default_port
):
return False return False
# Standard case: root URI must match # Standard case: root URI must match
return changed_port or changed_scheme return changed_port or changed_scheme
def resolve_redirects(self, resp, req, stream=False, timeout=None, def resolve_redirects(
verify=True, cert=None, proxies=None, yield_requests=False, **adapter_kwargs): self,
resp,
req,
stream=False,
timeout=None,
verify=True,
cert=None,
proxies=None,
yield_requests=False,
**adapter_kwargs,
):
"""Receives a Response. Returns a generator of Responses or Requests.""" """Receives a Response. Returns a generator of Responses or Requests."""
hist = [] # keep track of history hist = [] # keep track of history
@@ -159,19 +188,21 @@ class SessionRedirectMixin(object):
resp.raw.read(decode_content=False) resp.raw.read(decode_content=False)
if len(resp.history) >= self.max_redirects: if len(resp.history) >= self.max_redirects:
raise TooManyRedirects('Exceeded {} redirects.'.format(self.max_redirects), response=resp) raise TooManyRedirects(
f"Exceeded {self.max_redirects} redirects.", response=resp
)
# Release the connection back into the pool. # Release the connection back into the pool.
resp.close() resp.close()
# Handle redirection without scheme (see: RFC 1808 Section 4) # Handle redirection without scheme (see: RFC 1808 Section 4)
if url.startswith('//'): if url.startswith("//"):
parsed_rurl = urlparse(resp.url) parsed_rurl = urlparse(resp.url)
url = ':'.join([to_native_string(parsed_rurl.scheme), url]) url = ":".join([to_native_string(parsed_rurl.scheme), url])
# Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2) # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2)
parsed = urlparse(url) parsed = urlparse(url)
if parsed.fragment == '' and previous_fragment: if parsed.fragment == "" and previous_fragment:
parsed = parsed._replace(fragment=previous_fragment) parsed = parsed._replace(fragment=previous_fragment)
elif parsed.fragment: elif parsed.fragment:
previous_fragment = parsed.fragment previous_fragment = parsed.fragment
@@ -190,15 +221,18 @@ class SessionRedirectMixin(object):
self.rebuild_method(prepared_request, resp) self.rebuild_method(prepared_request, resp)
# https://github.com/psf/requests/issues/1084 # https://github.com/psf/requests/issues/1084
if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect): if resp.status_code not in (
codes.temporary_redirect,
codes.permanent_redirect,
):
# https://github.com/psf/requests/issues/3490 # https://github.com/psf/requests/issues/3490
purged_headers = ('Content-Length', 'Content-Type', 'Transfer-Encoding') purged_headers = ("Content-Length", "Content-Type", "Transfer-Encoding")
for header in purged_headers: for header in purged_headers:
prepared_request.headers.pop(header, None) prepared_request.headers.pop(header, None)
prepared_request.body = None prepared_request.body = None
headers = prepared_request.headers headers = prepared_request.headers
headers.pop('Cookie', None) headers.pop("Cookie", None)
# Extract any cookies sent on the response to the cookiejar # Extract any cookies sent on the response to the cookiejar
# in the new request. Because we've mutated our copied prepared # in the new request. Because we've mutated our copied prepared
@@ -214,9 +248,8 @@ class SessionRedirectMixin(object):
# A failed tell() sets `_body_position` to `object()`. This non-None # A failed tell() sets `_body_position` to `object()`. This non-None
# value ensures `rewindable` will be True, allowing us to raise an # value ensures `rewindable` will be True, allowing us to raise an
# UnrewindableBodyError, instead of hanging the connection. # UnrewindableBodyError, instead of hanging the connection.
rewindable = ( rewindable = prepared_request._body_position is not None and (
prepared_request._body_position is not None and "Content-Length" in headers or "Transfer-Encoding" in headers
('Content-Length' in headers or 'Transfer-Encoding' in headers)
) )
# Attempt to rewind consumed file-like object. # Attempt to rewind consumed file-like object.
@@ -238,7 +271,7 @@ class SessionRedirectMixin(object):
cert=cert, cert=cert,
proxies=proxies, proxies=proxies,
allow_redirects=False, allow_redirects=False,
**adapter_kwargs **adapter_kwargs,
) )
extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
@@ -255,10 +288,12 @@ class SessionRedirectMixin(object):
headers = prepared_request.headers headers = prepared_request.headers
url = prepared_request.url url = prepared_request.url
if 'Authorization' in headers and self.should_strip_auth(response.request.url, url): if "Authorization" in headers and self.should_strip_auth(
response.request.url, url
):
# If we get redirected to a new host, we should strip out any # If we get redirected to a new host, we should strip out any
# authentication headers. # authentication headers.
del headers['Authorization'] del headers["Authorization"]
# .netrc might have more auth for us on our new host. # .netrc might have more auth for us on our new host.
new_auth = get_netrc_auth(url) if self.trust_env else None new_auth = get_netrc_auth(url) if self.trust_env else None
@@ -281,8 +316,8 @@ class SessionRedirectMixin(object):
scheme = urlparse(prepared_request.url).scheme scheme = urlparse(prepared_request.url).scheme
new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env) new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env)
if 'Proxy-Authorization' in headers: if "Proxy-Authorization" in headers:
del headers['Proxy-Authorization'] del headers["Proxy-Authorization"]
try: try:
username, password = get_auth_from_url(new_proxies[scheme]) username, password = get_auth_from_url(new_proxies[scheme])
@@ -290,7 +325,7 @@ class SessionRedirectMixin(object):
username, password = None, None username, password = None, None
if username and password: if username and password:
headers['Proxy-Authorization'] = _basic_auth_str(username, password) headers["Proxy-Authorization"] = _basic_auth_str(username, password)
return new_proxies return new_proxies
@@ -301,18 +336,18 @@ class SessionRedirectMixin(object):
method = prepared_request.method method = prepared_request.method
# https://tools.ietf.org/html/rfc7231#section-6.4.4 # https://tools.ietf.org/html/rfc7231#section-6.4.4
if response.status_code == codes.see_other and method != 'HEAD': if response.status_code == codes.see_other and method != "HEAD":
method = 'GET' method = "GET"
# Do what the browsers do, despite standards... # Do what the browsers do, despite standards...
# First, turn 302s into GETs. # First, turn 302s into GETs.
if response.status_code == codes.found and method != 'HEAD': if response.status_code == codes.found and method != "HEAD":
method = 'GET' method = "GET"
# Second, if a POST is responded to with a 301, turn it into a GET. # Second, if a POST is responded to with a 301, turn it into a GET.
# This bizarre behaviour is explained in Issue 1704. # This bizarre behaviour is explained in Issue 1704.
if response.status_code == codes.moved and method == 'POST': if response.status_code == codes.moved and method == "POST":
method = 'GET' method = "GET"
prepared_request.method = method prepared_request.method = method
@@ -337,9 +372,18 @@ class Session(SessionRedirectMixin):
""" """
__attrs__ = [ __attrs__ = [
'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify', "headers",
'cert', 'adapters', 'stream', 'trust_env', "cookies",
'max_redirects', "auth",
"proxies",
"hooks",
"params",
"verify",
"cert",
"adapters",
"stream",
"trust_env",
"max_redirects",
] ]
def __init__(self): def __init__(self):
@@ -401,8 +445,8 @@ class Session(SessionRedirectMixin):
# Default connection adapters. # Default connection adapters.
self.adapters = OrderedDict() self.adapters = OrderedDict()
self.mount('https://', HTTPAdapter()) self.mount("https://", HTTPAdapter())
self.mount('http://', HTTPAdapter()) self.mount("http://", HTTPAdapter())
def __enter__(self): def __enter__(self):
return self return self
@@ -428,7 +472,8 @@ class Session(SessionRedirectMixin):
# Merge with session cookies # Merge with session cookies
merged_cookies = merge_cookies( merged_cookies = merge_cookies(
merge_cookies(RequestsCookieJar(), self.cookies), cookies) merge_cookies(RequestsCookieJar(), self.cookies), cookies
)
# Set environment's basic authentication if not explicitly set. # Set environment's basic authentication if not explicitly set.
auth = request.auth auth = request.auth
@@ -442,7 +487,9 @@ class Session(SessionRedirectMixin):
files=request.files, files=request.files,
data=request.data, data=request.data,
json=request.json, json=request.json,
headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict), headers=merge_setting(
request.headers, self.headers, dict_class=CaseInsensitiveDict
),
params=merge_setting(request.params, self.params), params=merge_setting(request.params, self.params),
auth=merge_setting(auth, self.auth), auth=merge_setting(auth, self.auth),
cookies=merged_cookies, cookies=merged_cookies,
@@ -450,10 +497,25 @@ class Session(SessionRedirectMixin):
) )
return p return p
def request(self, method, url, def request(
params=None, data=None, headers=None, cookies=None, files=None, self,
auth=None, timeout=None, allow_redirects=True, proxies=None, method,
hooks=None, stream=None, verify=None, cert=None, json=None): url,
params=None,
data=None,
headers=None,
cookies=None,
files=None,
auth=None,
timeout=None,
allow_redirects=True,
proxies=None,
hooks=None,
stream=None,
verify=None,
cert=None,
json=None,
):
"""Constructs a :class:`Request <Request>`, prepares it and sends it. """Constructs a :class:`Request <Request>`, prepares it and sends it.
Returns :class:`Response <Response>` object. Returns :class:`Response <Response>` object.
@@ -489,7 +551,7 @@ class Session(SessionRedirectMixin):
``False``, requests will accept any TLS certificate presented by ``False``, requests will accept any TLS certificate presented by
the server, and will ignore hostname mismatches and/or expired the server, and will ignore hostname mismatches and/or expired
certificates, which will make your application vulnerable to certificates, which will make your application vulnerable to
man-in-the-middle (MitM) attacks. Setting verify to ``False`` man-in-the-middle (MitM) attacks. Setting verify to ``False``
may be useful during local development or testing. may be useful during local development or testing.
:param cert: (optional) if String, path to ssl client cert file (.pem). :param cert: (optional) if String, path to ssl client cert file (.pem).
If Tuple, ('cert', 'key') pair. If Tuple, ('cert', 'key') pair.
@@ -518,8 +580,8 @@ class Session(SessionRedirectMixin):
# Send the request. # Send the request.
send_kwargs = { send_kwargs = {
'timeout': timeout, "timeout": timeout,
'allow_redirects': allow_redirects, "allow_redirects": allow_redirects,
} }
send_kwargs.update(settings) send_kwargs.update(settings)
resp = self.send(prep, **send_kwargs) resp = self.send(prep, **send_kwargs)
@@ -534,8 +596,8 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response :rtype: requests.Response
""" """
kwargs.setdefault('allow_redirects', True) kwargs.setdefault("allow_redirects", True)
return self.request('GET', url, **kwargs) return self.request("GET", url, **kwargs)
def options(self, url, **kwargs): def options(self, url, **kwargs):
r"""Sends a OPTIONS request. Returns :class:`Response` object. r"""Sends a OPTIONS request. Returns :class:`Response` object.
@@ -545,8 +607,8 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response :rtype: requests.Response
""" """
kwargs.setdefault('allow_redirects', True) kwargs.setdefault("allow_redirects", True)
return self.request('OPTIONS', url, **kwargs) return self.request("OPTIONS", url, **kwargs)
def head(self, url, **kwargs): def head(self, url, **kwargs):
r"""Sends a HEAD request. Returns :class:`Response` object. r"""Sends a HEAD request. Returns :class:`Response` object.
@@ -556,8 +618,8 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response :rtype: requests.Response
""" """
kwargs.setdefault('allow_redirects', False) kwargs.setdefault("allow_redirects", False)
return self.request('HEAD', url, **kwargs) return self.request("HEAD", url, **kwargs)
def post(self, url, data=None, json=None, **kwargs): def post(self, url, data=None, json=None, **kwargs):
r"""Sends a POST request. Returns :class:`Response` object. r"""Sends a POST request. Returns :class:`Response` object.
@@ -570,7 +632,7 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response :rtype: requests.Response
""" """
return self.request('POST', url, data=data, json=json, **kwargs) return self.request("POST", url, data=data, json=json, **kwargs)
def put(self, url, data=None, **kwargs): def put(self, url, data=None, **kwargs):
r"""Sends a PUT request. Returns :class:`Response` object. r"""Sends a PUT request. Returns :class:`Response` object.
@@ -582,7 +644,7 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response :rtype: requests.Response
""" """
return self.request('PUT', url, data=data, **kwargs) return self.request("PUT", url, data=data, **kwargs)
def patch(self, url, data=None, **kwargs): def patch(self, url, data=None, **kwargs):
r"""Sends a PATCH request. Returns :class:`Response` object. r"""Sends a PATCH request. Returns :class:`Response` object.
@@ -594,7 +656,7 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response :rtype: requests.Response
""" """
return self.request('PATCH', url, data=data, **kwargs) return self.request("PATCH", url, data=data, **kwargs)
def delete(self, url, **kwargs): def delete(self, url, **kwargs):
r"""Sends a DELETE request. Returns :class:`Response` object. r"""Sends a DELETE request. Returns :class:`Response` object.
@@ -604,7 +666,7 @@ class Session(SessionRedirectMixin):
:rtype: requests.Response :rtype: requests.Response
""" """
return self.request('DELETE', url, **kwargs) return self.request("DELETE", url, **kwargs)
def send(self, request, **kwargs): def send(self, request, **kwargs):
"""Send a given PreparedRequest. """Send a given PreparedRequest.
@@ -613,22 +675,20 @@ class Session(SessionRedirectMixin):
""" """
# Set defaults that the hooks can utilize to ensure they always have # Set defaults that the hooks can utilize to ensure they always have
# the correct parameters to reproduce the previous request. # the correct parameters to reproduce the previous request.
kwargs.setdefault('stream', self.stream) kwargs.setdefault("stream", self.stream)
kwargs.setdefault('verify', self.verify) kwargs.setdefault("verify", self.verify)
kwargs.setdefault('cert', self.cert) kwargs.setdefault("cert", self.cert)
if 'proxies' not in kwargs: if "proxies" not in kwargs:
kwargs['proxies'] = resolve_proxies( kwargs["proxies"] = resolve_proxies(request, self.proxies, self.trust_env)
request, self.proxies, self.trust_env
)
# It's possible that users might accidentally send a Request object. # It's possible that users might accidentally send a Request object.
# Guard against that specific failure case. # Guard against that specific failure case.
if isinstance(request, Request): if isinstance(request, Request):
raise ValueError('You can only send PreparedRequests.') raise ValueError("You can only send PreparedRequests.")
# Set up variables needed for resolve_redirects and dispatching of hooks # Set up variables needed for resolve_redirects and dispatching of hooks
allow_redirects = kwargs.pop('allow_redirects', True) allow_redirects = kwargs.pop("allow_redirects", True)
stream = kwargs.get('stream') stream = kwargs.get("stream")
hooks = request.hooks hooks = request.hooks
# Get the appropriate adapter to use # Get the appropriate adapter to use
@@ -645,7 +705,7 @@ class Session(SessionRedirectMixin):
r.elapsed = timedelta(seconds=elapsed) r.elapsed = timedelta(seconds=elapsed)
# Response manipulation hooks # Response manipulation hooks
r = dispatch_hook('response', hooks, r, **kwargs) r = dispatch_hook("response", hooks, r, **kwargs)
# Persist cookies # Persist cookies
if r.history: if r.history:
@@ -675,7 +735,9 @@ class Session(SessionRedirectMixin):
# If redirects aren't being followed, store the response on the Request for Response.next(). # If redirects aren't being followed, store the response on the Request for Response.next().
if not allow_redirects: if not allow_redirects:
try: try:
r._next = next(self.resolve_redirects(r, request, yield_requests=True, **kwargs)) r._next = next(
self.resolve_redirects(r, request, yield_requests=True, **kwargs)
)
except StopIteration: except StopIteration:
pass pass
@@ -693,7 +755,7 @@ class Session(SessionRedirectMixin):
# Gather clues from the surrounding environment. # Gather clues from the surrounding environment.
if self.trust_env: if self.trust_env:
# Set environment's proxies. # Set environment's proxies.
no_proxy = proxies.get('no_proxy') if proxies is not None else None no_proxy = proxies.get("no_proxy") if proxies is not None else None
env_proxies = get_environ_proxies(url, no_proxy=no_proxy) env_proxies = get_environ_proxies(url, no_proxy=no_proxy)
for (k, v) in env_proxies.items(): for (k, v) in env_proxies.items():
proxies.setdefault(k, v) proxies.setdefault(k, v)
@@ -702,8 +764,8 @@ class Session(SessionRedirectMixin):
# and be compatible with cURL. # and be compatible with cURL.
if verify is True or verify is None: if verify is True or verify is None:
verify = ( verify = (
os.environ.get('REQUESTS_CA_BUNDLE') os.environ.get("REQUESTS_CA_BUNDLE")
or os.environ.get('CURL_CA_BUNDLE') or os.environ.get("CURL_CA_BUNDLE")
or verify or verify
) )
@@ -713,12 +775,7 @@ class Session(SessionRedirectMixin):
verify = merge_setting(verify, self.verify) verify = merge_setting(verify, self.verify)
cert = merge_setting(cert, self.cert) cert = merge_setting(cert, self.cert)
return { return {"proxies": proxies, "stream": stream, "verify": verify, "cert": cert}
'proxies': proxies,
'stream': stream,
'verify': verify,
'cert': cert
}
def get_adapter(self, url): def get_adapter(self, url):
""" """
@@ -732,7 +789,7 @@ class Session(SessionRedirectMixin):
return adapter return adapter
# Nothing matches :-/ # Nothing matches :-/
raise InvalidSchema("No connection adapters were found for {!r}".format(url)) raise InvalidSchema(f"No connection adapters were found for {url!r}")
def close(self): def close(self):
"""Closes all adapters and as such the session""" """Closes all adapters and as such the session"""
+87 -82
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
r""" r"""
The ``codes`` object defines a mapping from common names for HTTP statuses The ``codes`` object defines a mapping from common names for HTTP statuses
to their numerical codes, accessible either as attributes or as dictionary to their numerical codes, accessible either as attributes or as dictionary
@@ -23,101 +21,108 @@ the names are allowed. For example, ``codes.ok``, ``codes.OK``, and
from .structures import LookupDict from .structures import LookupDict
_codes = { _codes = {
# Informational. # Informational.
100: ('continue',), 100: ("continue",),
101: ('switching_protocols',), 101: ("switching_protocols",),
102: ('processing',), 102: ("processing",),
103: ('checkpoint',), 103: ("checkpoint",),
122: ('uri_too_long', 'request_uri_too_long'), 122: ("uri_too_long", "request_uri_too_long"),
200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', ''), 200: ("ok", "okay", "all_ok", "all_okay", "all_good", "\\o/", ""),
201: ('created',), 201: ("created",),
202: ('accepted',), 202: ("accepted",),
203: ('non_authoritative_info', 'non_authoritative_information'), 203: ("non_authoritative_info", "non_authoritative_information"),
204: ('no_content',), 204: ("no_content",),
205: ('reset_content', 'reset'), 205: ("reset_content", "reset"),
206: ('partial_content', 'partial'), 206: ("partial_content", "partial"),
207: ('multi_status', 'multiple_status', 'multi_stati', 'multiple_stati'), 207: ("multi_status", "multiple_status", "multi_stati", "multiple_stati"),
208: ('already_reported',), 208: ("already_reported",),
226: ('im_used',), 226: ("im_used",),
# Redirection. # Redirection.
300: ('multiple_choices',), 300: ("multiple_choices",),
301: ('moved_permanently', 'moved', '\\o-'), 301: ("moved_permanently", "moved", "\\o-"),
302: ('found',), 302: ("found",),
303: ('see_other', 'other'), 303: ("see_other", "other"),
304: ('not_modified',), 304: ("not_modified",),
305: ('use_proxy',), 305: ("use_proxy",),
306: ('switch_proxy',), 306: ("switch_proxy",),
307: ('temporary_redirect', 'temporary_moved', 'temporary'), 307: ("temporary_redirect", "temporary_moved", "temporary"),
308: ('permanent_redirect', 308: (
'resume_incomplete', 'resume',), # These 2 to be removed in 3.0 "permanent_redirect",
"resume_incomplete",
"resume",
), # "resume" and "resume_incomplete" to be removed in 3.0
# Client Error. # Client Error.
400: ('bad_request', 'bad'), 400: ("bad_request", "bad"),
401: ('unauthorized',), 401: ("unauthorized",),
402: ('payment_required', 'payment'), 402: ("payment_required", "payment"),
403: ('forbidden',), 403: ("forbidden",),
404: ('not_found', '-o-'), 404: ("not_found", "-o-"),
405: ('method_not_allowed', 'not_allowed'), 405: ("method_not_allowed", "not_allowed"),
406: ('not_acceptable',), 406: ("not_acceptable",),
407: ('proxy_authentication_required', 'proxy_auth', 'proxy_authentication'), 407: ("proxy_authentication_required", "proxy_auth", "proxy_authentication"),
408: ('request_timeout', 'timeout'), 408: ("request_timeout", "timeout"),
409: ('conflict',), 409: ("conflict",),
410: ('gone',), 410: ("gone",),
411: ('length_required',), 411: ("length_required",),
412: ('precondition_failed', 'precondition'), 412: ("precondition_failed", "precondition"),
413: ('request_entity_too_large',), 413: ("request_entity_too_large",),
414: ('request_uri_too_large',), 414: ("request_uri_too_large",),
415: ('unsupported_media_type', 'unsupported_media', 'media_type'), 415: ("unsupported_media_type", "unsupported_media", "media_type"),
416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'), 416: (
417: ('expectation_failed',), "requested_range_not_satisfiable",
418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'), "requested_range",
421: ('misdirected_request',), "range_not_satisfiable",
422: ('unprocessable_entity', 'unprocessable'), ),
423: ('locked',), 417: ("expectation_failed",),
424: ('failed_dependency', 'dependency'), 418: ("im_a_teapot", "teapot", "i_am_a_teapot"),
425: ('unordered_collection', 'unordered'), 421: ("misdirected_request",),
426: ('upgrade_required', 'upgrade'), 422: ("unprocessable_entity", "unprocessable"),
428: ('precondition_required', 'precondition'), 423: ("locked",),
429: ('too_many_requests', 'too_many'), 424: ("failed_dependency", "dependency"),
431: ('header_fields_too_large', 'fields_too_large'), 425: ("unordered_collection", "unordered"),
444: ('no_response', 'none'), 426: ("upgrade_required", "upgrade"),
449: ('retry_with', 'retry'), 428: ("precondition_required", "precondition"),
450: ('blocked_by_windows_parental_controls', 'parental_controls'), 429: ("too_many_requests", "too_many"),
451: ('unavailable_for_legal_reasons', 'legal_reasons'), 431: ("header_fields_too_large", "fields_too_large"),
499: ('client_closed_request',), 444: ("no_response", "none"),
449: ("retry_with", "retry"),
450: ("blocked_by_windows_parental_controls", "parental_controls"),
451: ("unavailable_for_legal_reasons", "legal_reasons"),
499: ("client_closed_request",),
# Server Error. # Server Error.
500: ('internal_server_error', 'server_error', '/o\\', ''), 500: ("internal_server_error", "server_error", "/o\\", ""),
501: ('not_implemented',), 501: ("not_implemented",),
502: ('bad_gateway',), 502: ("bad_gateway",),
503: ('service_unavailable', 'unavailable'), 503: ("service_unavailable", "unavailable"),
504: ('gateway_timeout',), 504: ("gateway_timeout",),
505: ('http_version_not_supported', 'http_version'), 505: ("http_version_not_supported", "http_version"),
506: ('variant_also_negotiates',), 506: ("variant_also_negotiates",),
507: ('insufficient_storage',), 507: ("insufficient_storage",),
509: ('bandwidth_limit_exceeded', 'bandwidth'), 509: ("bandwidth_limit_exceeded", "bandwidth"),
510: ('not_extended',), 510: ("not_extended",),
511: ('network_authentication_required', 'network_auth', 'network_authentication'), 511: ("network_authentication_required", "network_auth", "network_authentication"),
} }
codes = LookupDict(name='status_codes') codes = LookupDict(name="status_codes")
def _init(): def _init():
for code, titles in _codes.items(): for code, titles in _codes.items():
for title in titles: for title in titles:
setattr(codes, title, code) setattr(codes, title, code)
if not title.startswith(('\\', '/')): if not title.startswith(("\\", "/")):
setattr(codes, title.upper(), code) setattr(codes, title.upper(), code)
def doc(code): def doc(code):
names = ', '.join('``%s``' % n for n in _codes[code]) names = ", ".join(f"``{n}``" for n in _codes[code])
return '* %d: %s' % (code, names) return "* %d: %s" % (code, names)
global __doc__ global __doc__
__doc__ = (__doc__ + '\n' + __doc__ = (
'\n'.join(doc(code) for code in sorted(_codes)) __doc__ + "\n" + "\n".join(doc(code) for code in sorted(_codes))
if __doc__ is not None else None) if __doc__ is not None
else None
)
_init() _init()
+3 -9
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
""" """
requests.structures requests.structures
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
@@ -64,11 +62,7 @@ class CaseInsensitiveDict(MutableMapping):
def lower_items(self): def lower_items(self):
"""Like iteritems(), but with all lowercase keys.""" """Like iteritems(), but with all lowercase keys."""
return ( return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items())
(lowerkey, keyval[1])
for (lowerkey, keyval)
in self._store.items()
)
def __eq__(self, other): def __eq__(self, other):
if isinstance(other, Mapping): if isinstance(other, Mapping):
@@ -91,10 +85,10 @@ class LookupDict(dict):
def __init__(self, name=None): def __init__(self, name=None):
self.name = name self.name = name
super(LookupDict, self).__init__() super().__init__()
def __repr__(self): def __repr__(self):
return '<lookup \'%s\'>' % (self.name) return f"<lookup '{self.name}'>"
def __getitem__(self, key): def __getitem__(self, key):
# We allow fall-through here, so values default to None # We allow fall-through here, so values default to None
+193 -160
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
""" """
requests.utils requests.utils
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~
@@ -20,28 +18,46 @@ import tempfile
import warnings import warnings
import zipfile import zipfile
from collections import OrderedDict from collections import OrderedDict
from urllib3.util import make_headers
from urllib3.util import parse_url
from .__version__ import __version__ from urllib3.util import make_headers, parse_url
from . import certs from . import certs
from .__version__ import __version__
# to_native_string is unused here, but imported here for backwards compatibility # to_native_string is unused here, but imported here for backwards compatibility
from ._internal_utils import to_native_string from ._internal_utils import to_native_string # noqa: F401
from .compat import (
Mapping,
basestring,
bytes,
getproxies,
getproxies_environment,
integer_types,
)
from .compat import parse_http_list as _parse_list_header from .compat import parse_http_list as _parse_list_header
from .compat import ( from .compat import (
quote, urlparse, bytes, str, unquote, getproxies, proxy_bypass,
proxy_bypass, urlunparse, basestring, integer_types, proxy_bypass_environment,
proxy_bypass_environment, getproxies_environment, Mapping) quote,
str,
unquote,
urlparse,
urlunparse,
)
from .cookies import cookiejar_from_dict from .cookies import cookiejar_from_dict
from .structures import CaseInsensitiveDict
from .exceptions import ( from .exceptions import (
InvalidURL, InvalidHeader, FileModeWarning, UnrewindableBodyError) FileModeWarning,
InvalidHeader,
InvalidURL,
UnrewindableBodyError,
)
from .structures import CaseInsensitiveDict
NETRC_FILES = ('.netrc', '_netrc') NETRC_FILES = (".netrc", "_netrc")
DEFAULT_CA_BUNDLE_PATH = certs.where() DEFAULT_CA_BUNDLE_PATH = certs.where()
DEFAULT_PORTS = {'http': 80, 'https': 443} DEFAULT_PORTS = {"http": 80, "https": 443}
# Ensure that ', ' is used to preserve previous delimiter behavior. # Ensure that ', ' is used to preserve previous delimiter behavior.
DEFAULT_ACCEPT_ENCODING = ", ".join( DEFAULT_ACCEPT_ENCODING = ", ".join(
@@ -49,7 +65,7 @@ DEFAULT_ACCEPT_ENCODING = ", ".join(
) )
if sys.platform == 'win32': if sys.platform == "win32":
# provide a proxy_bypass version on Windows without DNS lookups # provide a proxy_bypass version on Windows without DNS lookups
def proxy_bypass_registry(host): def proxy_bypass_registry(host):
@@ -59,14 +75,14 @@ if sys.platform == 'win32':
return False return False
try: try:
internetSettings = winreg.OpenKey(winreg.HKEY_CURRENT_USER, internetSettings = winreg.OpenKey(
r'Software\Microsoft\Windows\CurrentVersion\Internet Settings') winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
)
# ProxyEnable could be REG_SZ or REG_DWORD, normalizing it # ProxyEnable could be REG_SZ or REG_DWORD, normalizing it
proxyEnable = int(winreg.QueryValueEx(internetSettings, proxyEnable = int(winreg.QueryValueEx(internetSettings, "ProxyEnable")[0])
'ProxyEnable')[0])
# ProxyOverride is almost always a string # ProxyOverride is almost always a string
proxyOverride = winreg.QueryValueEx(internetSettings, proxyOverride = winreg.QueryValueEx(internetSettings, "ProxyOverride")[0]
'ProxyOverride')[0]
except OSError: except OSError:
return False return False
if not proxyEnable or not proxyOverride: if not proxyEnable or not proxyOverride:
@@ -75,15 +91,15 @@ if sys.platform == 'win32':
# make a check value list from the registry entry: replace the # make a check value list from the registry entry: replace the
# '<local>' string by the localhost entry and the corresponding # '<local>' string by the localhost entry and the corresponding
# canonical entry. # canonical entry.
proxyOverride = proxyOverride.split(';') proxyOverride = proxyOverride.split(";")
# now check if we match one of the registry values. # now check if we match one of the registry values.
for test in proxyOverride: for test in proxyOverride:
if test == '<local>': if test == "<local>":
if '.' not in host: if "." not in host:
return True return True
test = test.replace(".", r"\.") # mask dots test = test.replace(".", r"\.") # mask dots
test = test.replace("*", r".*") # change glob sequence test = test.replace("*", r".*") # change glob sequence
test = test.replace("?", r".") # change glob char test = test.replace("?", r".") # change glob char
if re.match(test, host, re.I): if re.match(test, host, re.I):
return True return True
return False return False
@@ -103,7 +119,7 @@ if sys.platform == 'win32':
def dict_to_sequence(d): def dict_to_sequence(d):
"""Returns an internal sequence dictionary update.""" """Returns an internal sequence dictionary update."""
if hasattr(d, 'items'): if hasattr(d, "items"):
d = d.items() d = d.items()
return d return d
@@ -113,13 +129,13 @@ def super_len(o):
total_length = None total_length = None
current_position = 0 current_position = 0
if hasattr(o, '__len__'): if hasattr(o, "__len__"):
total_length = len(o) total_length = len(o)
elif hasattr(o, 'len'): elif hasattr(o, "len"):
total_length = o.len total_length = o.len
elif hasattr(o, 'fileno'): elif hasattr(o, "fileno"):
try: try:
fileno = o.fileno() fileno = o.fileno()
except (io.UnsupportedOperation, AttributeError): except (io.UnsupportedOperation, AttributeError):
@@ -132,21 +148,23 @@ def super_len(o):
# Having used fstat to determine the file length, we need to # Having used fstat to determine the file length, we need to
# confirm that this file was opened up in binary mode. # confirm that this file was opened up in binary mode.
if 'b' not in o.mode: if "b" not in o.mode:
warnings.warn(( warnings.warn(
"Requests has determined the content-length for this " (
"request using the binary size of the file: however, the " "Requests has determined the content-length for this "
"file has been opened in text mode (i.e. without the 'b' " "request using the binary size of the file: however, the "
"flag in the mode). This may lead to an incorrect " "file has been opened in text mode (i.e. without the 'b' "
"content-length. In Requests 3.0, support will be removed " "flag in the mode). This may lead to an incorrect "
"for files in text mode."), "content-length. In Requests 3.0, support will be removed "
FileModeWarning "for files in text mode."
),
FileModeWarning,
) )
if hasattr(o, 'tell'): if hasattr(o, "tell"):
try: try:
current_position = o.tell() current_position = o.tell()
except (OSError, IOError): except OSError:
# This can happen in some weird situations, such as when the file # This can happen in some weird situations, such as when the file
# is actually a special file descriptor like stdin. In this # is actually a special file descriptor like stdin. In this
# instance, we don't know what the length is, so set it to zero and # instance, we don't know what the length is, so set it to zero and
@@ -154,7 +172,7 @@ def super_len(o):
if total_length is not None: if total_length is not None:
current_position = total_length current_position = total_length
else: else:
if hasattr(o, 'seek') and total_length is None: if hasattr(o, "seek") and total_length is None:
# StringIO and BytesIO have seek but no usable fileno # StringIO and BytesIO have seek but no usable fileno
try: try:
# seek to end of file # seek to end of file
@@ -164,7 +182,7 @@ def super_len(o):
# seek back to current position to support # seek back to current position to support
# partially read file-like objects # partially read file-like objects
o.seek(current_position or 0) o.seek(current_position or 0)
except (OSError, IOError): except OSError:
total_length = 0 total_length = 0
if total_length is None: if total_length is None:
@@ -176,14 +194,14 @@ def super_len(o):
def get_netrc_auth(url, raise_errors=False): def get_netrc_auth(url, raise_errors=False):
"""Returns the Requests tuple auth for a given url from netrc.""" """Returns the Requests tuple auth for a given url from netrc."""
netrc_file = os.environ.get('NETRC') netrc_file = os.environ.get("NETRC")
if netrc_file is not None: if netrc_file is not None:
netrc_locations = (netrc_file,) netrc_locations = (netrc_file,)
else: else:
netrc_locations = ('~/{}'.format(f) for f in NETRC_FILES) netrc_locations = (f"~/{f}" for f in NETRC_FILES)
try: try:
from netrc import netrc, NetrcParseError from netrc import NetrcParseError, netrc
netrc_path = None netrc_path = None
@@ -208,18 +226,18 @@ def get_netrc_auth(url, raise_errors=False):
# Strip port numbers from netloc. This weird `if...encode`` dance is # Strip port numbers from netloc. This weird `if...encode`` dance is
# used for Python 3.2, which doesn't support unicode literals. # used for Python 3.2, which doesn't support unicode literals.
splitstr = b':' splitstr = b":"
if isinstance(url, str): if isinstance(url, str):
splitstr = splitstr.decode('ascii') splitstr = splitstr.decode("ascii")
host = ri.netloc.split(splitstr)[0] host = ri.netloc.split(splitstr)[0]
try: try:
_netrc = netrc(netrc_path).authenticators(host) _netrc = netrc(netrc_path).authenticators(host)
if _netrc: if _netrc:
# Return with login / password # Return with login / password
login_i = (0 if _netrc[0] else 1) login_i = 0 if _netrc[0] else 1
return (_netrc[login_i], _netrc[2]) return (_netrc[login_i], _netrc[2])
except (NetrcParseError, IOError): except (NetrcParseError, OSError):
# If there was a parsing error or a permissions issue reading the file, # If there was a parsing error or a permissions issue reading the file,
# we'll just skip netrc auth unless explicitly asked to raise errors. # we'll just skip netrc auth unless explicitly asked to raise errors.
if raise_errors: if raise_errors:
@@ -232,9 +250,8 @@ def get_netrc_auth(url, raise_errors=False):
def guess_filename(obj): def guess_filename(obj):
"""Tries to guess the filename of the given object.""" """Tries to guess the filename of the given object."""
name = getattr(obj, 'name', None) name = getattr(obj, "name", None)
if (name and isinstance(name, basestring) and name[0] != '<' and if name and isinstance(name, basestring) and name[0] != "<" and name[-1] != ">":
name[-1] != '>'):
return os.path.basename(name) return os.path.basename(name)
@@ -256,7 +273,7 @@ def extract_zipped_paths(path):
# If we don't check for an empty prefix after the split (in other words, archive remains unchanged after the split), # If we don't check for an empty prefix after the split (in other words, archive remains unchanged after the split),
# we _can_ end up in an infinite loop on a rare corner case affecting a small number of users # we _can_ end up in an infinite loop on a rare corner case affecting a small number of users
break break
member = '/'.join([prefix, member]) member = "/".join([prefix, member])
if not zipfile.is_zipfile(archive): if not zipfile.is_zipfile(archive):
return path return path
@@ -267,7 +284,7 @@ def extract_zipped_paths(path):
# we have a valid zip archive and a valid member of that archive # we have a valid zip archive and a valid member of that archive
tmp = tempfile.gettempdir() tmp = tempfile.gettempdir()
extracted_path = os.path.join(tmp, member.split('/')[-1]) extracted_path = os.path.join(tmp, member.split("/")[-1])
if not os.path.exists(extracted_path): if not os.path.exists(extracted_path):
# use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition # use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition
with atomic_open(extracted_path) as file_handler: with atomic_open(extracted_path) as file_handler:
@@ -280,7 +297,7 @@ def atomic_open(filename):
"""Write a file to the disk in an atomic fashion""" """Write a file to the disk in an atomic fashion"""
tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename)) tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename))
try: try:
with os.fdopen(tmp_descriptor, 'wb') as tmp_handler: with os.fdopen(tmp_descriptor, "wb") as tmp_handler:
yield tmp_handler yield tmp_handler
os.replace(tmp_name, filename) os.replace(tmp_name, filename)
except BaseException: except BaseException:
@@ -310,7 +327,7 @@ def from_key_val_list(value):
return None return None
if isinstance(value, (str, bytes, bool, int)): if isinstance(value, (str, bytes, bool, int)):
raise ValueError('cannot encode objects that are not 2-tuples') raise ValueError("cannot encode objects that are not 2-tuples")
return OrderedDict(value) return OrderedDict(value)
@@ -336,7 +353,7 @@ def to_key_val_list(value):
return None return None
if isinstance(value, (str, bytes, bool, int)): if isinstance(value, (str, bytes, bool, int)):
raise ValueError('cannot encode objects that are not 2-tuples') raise ValueError("cannot encode objects that are not 2-tuples")
if isinstance(value, Mapping): if isinstance(value, Mapping):
value = value.items() value = value.items()
@@ -401,10 +418,10 @@ def parse_dict_header(value):
""" """
result = {} result = {}
for item in _parse_list_header(value): for item in _parse_list_header(value):
if '=' not in item: if "=" not in item:
result[item] = None result[item] = None
continue continue
name, value = item.split('=', 1) name, value = item.split("=", 1)
if value[:1] == value[-1:] == '"': if value[:1] == value[-1:] == '"':
value = unquote_header_value(value[1:-1]) value = unquote_header_value(value[1:-1])
result[name] = value result[name] = value
@@ -432,8 +449,8 @@ def unquote_header_value(value, is_filename=False):
# replace sequence below on a UNC path has the effect of turning # replace sequence below on a UNC path has the effect of turning
# the leading double slash into a single slash and then # the leading double slash into a single slash and then
# _fix_ie_filename() doesn't work correctly. See #458. # _fix_ie_filename() doesn't work correctly. See #458.
if not is_filename or value[:2] != '\\\\': if not is_filename or value[:2] != "\\\\":
return value.replace('\\\\', '\\').replace('\\"', '"') return value.replace("\\\\", "\\").replace('\\"', '"')
return value return value
@@ -468,19 +485,24 @@ def get_encodings_from_content(content):
:param content: bytestring to extract encodings from. :param content: bytestring to extract encodings from.
""" """
warnings.warn(( warnings.warn(
'In requests 3.0, get_encodings_from_content will be removed. For ' (
'more information, please see the discussion on issue #2266. (This' "In requests 3.0, get_encodings_from_content will be removed. For "
' warning should only appear once.)'), "more information, please see the discussion on issue #2266. (This"
DeprecationWarning) " warning should only appear once.)"
),
DeprecationWarning,
)
charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I) charset_re = re.compile(r'<meta.*?charset=["\']*(.+?)["\'>]', flags=re.I)
pragma_re = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I) pragma_re = re.compile(r'<meta.*?content=["\']*;?charset=(.+?)["\'>]', flags=re.I)
xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]') xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]')
return (charset_re.findall(content) + return (
pragma_re.findall(content) + charset_re.findall(content)
xml_re.findall(content)) + pragma_re.findall(content)
+ xml_re.findall(content)
)
def _parse_content_type_header(header): def _parse_content_type_header(header):
@@ -491,7 +513,7 @@ def _parse_content_type_header(header):
parameters parameters
""" """
tokens = header.split(';') tokens = header.split(";")
content_type, params = tokens[0].strip(), tokens[1:] content_type, params = tokens[0].strip(), tokens[1:]
params_dict = {} params_dict = {}
items_to_strip = "\"' " items_to_strip = "\"' "
@@ -503,7 +525,7 @@ def _parse_content_type_header(header):
index_of_equals = param.find("=") index_of_equals = param.find("=")
if index_of_equals != -1: if index_of_equals != -1:
key = param[:index_of_equals].strip(items_to_strip) key = param[:index_of_equals].strip(items_to_strip)
value = param[index_of_equals + 1:].strip(items_to_strip) value = param[index_of_equals + 1 :].strip(items_to_strip)
params_dict[key.lower()] = value params_dict[key.lower()] = value
return content_type, params_dict return content_type, params_dict
@@ -515,38 +537,37 @@ def get_encoding_from_headers(headers):
:rtype: str :rtype: str
""" """
content_type = headers.get('content-type') content_type = headers.get("content-type")
if not content_type: if not content_type:
return None return None
content_type, params = _parse_content_type_header(content_type) content_type, params = _parse_content_type_header(content_type)
if 'charset' in params: if "charset" in params:
return params['charset'].strip("'\"") return params["charset"].strip("'\"")
if 'text' in content_type: if "text" in content_type:
return 'ISO-8859-1' return "ISO-8859-1"
if 'application/json' in content_type: if "application/json" in content_type:
# Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset # Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset
return 'utf-8' return "utf-8"
def stream_decode_response_unicode(iterator, r): def stream_decode_response_unicode(iterator, r):
"""Stream decodes a iterator.""" """Stream decodes a iterator."""
if r.encoding is None: if r.encoding is None:
for item in iterator: yield from iterator
yield item
return return
decoder = codecs.getincrementaldecoder(r.encoding)(errors='replace') decoder = codecs.getincrementaldecoder(r.encoding)(errors="replace")
for chunk in iterator: for chunk in iterator:
rv = decoder.decode(chunk) rv = decoder.decode(chunk)
if rv: if rv:
yield rv yield rv
rv = decoder.decode(b'', final=True) rv = decoder.decode(b"", final=True)
if rv: if rv:
yield rv yield rv
@@ -557,7 +578,7 @@ def iter_slices(string, slice_length):
if slice_length is None or slice_length <= 0: if slice_length is None or slice_length <= 0:
slice_length = len(string) slice_length = len(string)
while pos < len(string): while pos < len(string):
yield string[pos:pos + slice_length] yield string[pos : pos + slice_length]
pos += slice_length pos += slice_length
@@ -573,11 +594,14 @@ def get_unicode_from_response(r):
:rtype: str :rtype: str
""" """
warnings.warn(( warnings.warn(
'In requests 3.0, get_unicode_from_response will be removed. For ' (
'more information, please see the discussion on issue #2266. (This' "In requests 3.0, get_unicode_from_response will be removed. For "
' warning should only appear once.)'), "more information, please see the discussion on issue #2266. (This"
DeprecationWarning) " warning should only appear once.)"
),
DeprecationWarning,
)
tried_encodings = [] tried_encodings = []
@@ -592,14 +616,15 @@ def get_unicode_from_response(r):
# Fall back: # Fall back:
try: try:
return str(r.content, encoding, errors='replace') return str(r.content, encoding, errors="replace")
except TypeError: except TypeError:
return r.content return r.content
# The unreserved URI characters (RFC 3986) # The unreserved URI characters (RFC 3986)
UNRESERVED_SET = frozenset( UNRESERVED_SET = frozenset(
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~") "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~"
)
def unquote_unreserved(uri): def unquote_unreserved(uri):
@@ -608,22 +633,22 @@ def unquote_unreserved(uri):
:rtype: str :rtype: str
""" """
parts = uri.split('%') parts = uri.split("%")
for i in range(1, len(parts)): for i in range(1, len(parts)):
h = parts[i][0:2] h = parts[i][0:2]
if len(h) == 2 and h.isalnum(): if len(h) == 2 and h.isalnum():
try: try:
c = chr(int(h, 16)) c = chr(int(h, 16))
except ValueError: except ValueError:
raise InvalidURL("Invalid percent-escape sequence: '%s'" % h) raise InvalidURL(f"Invalid percent-escape sequence: '{h}'")
if c in UNRESERVED_SET: if c in UNRESERVED_SET:
parts[i] = c + parts[i][2:] parts[i] = c + parts[i][2:]
else: else:
parts[i] = '%' + parts[i] parts[i] = f"%{parts[i]}"
else: else:
parts[i] = '%' + parts[i] parts[i] = f"%{parts[i]}"
return ''.join(parts) return "".join(parts)
def requote_uri(uri): def requote_uri(uri):
@@ -656,10 +681,10 @@ def address_in_network(ip, net):
:rtype: bool :rtype: bool
""" """
ipaddr = struct.unpack('=L', socket.inet_aton(ip))[0] ipaddr = struct.unpack("=L", socket.inet_aton(ip))[0]
netaddr, bits = net.split('/') netaddr, bits = net.split("/")
netmask = struct.unpack('=L', socket.inet_aton(dotted_netmask(int(bits))))[0] netmask = struct.unpack("=L", socket.inet_aton(dotted_netmask(int(bits))))[0]
network = struct.unpack('=L', socket.inet_aton(netaddr))[0] & netmask network = struct.unpack("=L", socket.inet_aton(netaddr))[0] & netmask
return (ipaddr & netmask) == (network & netmask) return (ipaddr & netmask) == (network & netmask)
@@ -670,8 +695,8 @@ def dotted_netmask(mask):
:rtype: str :rtype: str
""" """
bits = 0xffffffff ^ (1 << 32 - mask) - 1 bits = 0xFFFFFFFF ^ (1 << 32 - mask) - 1
return socket.inet_ntoa(struct.pack('>I', bits)) return socket.inet_ntoa(struct.pack(">I", bits))
def is_ipv4_address(string_ip): def is_ipv4_address(string_ip):
@@ -680,7 +705,7 @@ def is_ipv4_address(string_ip):
""" """
try: try:
socket.inet_aton(string_ip) socket.inet_aton(string_ip)
except socket.error: except OSError:
return False return False
return True return True
@@ -691,9 +716,9 @@ def is_valid_cidr(string_network):
:rtype: bool :rtype: bool
""" """
if string_network.count('/') == 1: if string_network.count("/") == 1:
try: try:
mask = int(string_network.split('/')[1]) mask = int(string_network.split("/")[1])
except ValueError: except ValueError:
return False return False
@@ -701,8 +726,8 @@ def is_valid_cidr(string_network):
return False return False
try: try:
socket.inet_aton(string_network.split('/')[0]) socket.inet_aton(string_network.split("/")[0])
except socket.error: except OSError:
return False return False
else: else:
return False return False
@@ -739,13 +764,14 @@ def should_bypass_proxies(url, no_proxy):
""" """
# Prioritize lowercase environment variables over uppercase # Prioritize lowercase environment variables over uppercase
# to keep a consistent behaviour with other http projects (curl, wget). # to keep a consistent behaviour with other http projects (curl, wget).
get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper()) def get_proxy(key):
return os.environ.get(key) or os.environ.get(key.upper())
# First check whether no_proxy is defined. If it is, check that the URL # First check whether no_proxy is defined. If it is, check that the URL
# we're getting isn't in the no_proxy list. # we're getting isn't in the no_proxy list.
no_proxy_arg = no_proxy no_proxy_arg = no_proxy
if no_proxy is None: if no_proxy is None:
no_proxy = get_proxy('no_proxy') no_proxy = get_proxy("no_proxy")
parsed = urlparse(url) parsed = urlparse(url)
if parsed.hostname is None: if parsed.hostname is None:
@@ -755,9 +781,7 @@ def should_bypass_proxies(url, no_proxy):
if no_proxy: if no_proxy:
# We need to check whether we match here. We need to see if we match # We need to check whether we match here. We need to see if we match
# the end of the hostname, both with and without the port. # the end of the hostname, both with and without the port.
no_proxy = ( no_proxy = (host for host in no_proxy.replace(" ", "").split(",") if host)
host for host in no_proxy.replace(' ', '').split(',') if host
)
if is_ipv4_address(parsed.hostname): if is_ipv4_address(parsed.hostname):
for proxy_ip in no_proxy: for proxy_ip in no_proxy:
@@ -771,7 +795,7 @@ def should_bypass_proxies(url, no_proxy):
else: else:
host_with_port = parsed.hostname host_with_port = parsed.hostname
if parsed.port: if parsed.port:
host_with_port += ':{}'.format(parsed.port) host_with_port += f":{parsed.port}"
for host in no_proxy: for host in no_proxy:
if parsed.hostname.endswith(host) or host_with_port.endswith(host): if parsed.hostname.endswith(host) or host_with_port.endswith(host):
@@ -779,7 +803,7 @@ def should_bypass_proxies(url, no_proxy):
# to apply the proxies on this URL. # to apply the proxies on this URL.
return True return True
with set_environ('no_proxy', no_proxy_arg): with set_environ("no_proxy", no_proxy_arg):
# parsed.hostname can be `None` in cases such as a file URI. # parsed.hostname can be `None` in cases such as a file URI.
try: try:
bypass = proxy_bypass(parsed.hostname) bypass = proxy_bypass(parsed.hostname)
@@ -813,13 +837,13 @@ def select_proxy(url, proxies):
proxies = proxies or {} proxies = proxies or {}
urlparts = urlparse(url) urlparts = urlparse(url)
if urlparts.hostname is None: if urlparts.hostname is None:
return proxies.get(urlparts.scheme, proxies.get('all')) return proxies.get(urlparts.scheme, proxies.get("all"))
proxy_keys = [ proxy_keys = [
urlparts.scheme + '://' + urlparts.hostname, urlparts.scheme + "://" + urlparts.hostname,
urlparts.scheme, urlparts.scheme,
'all://' + urlparts.hostname, "all://" + urlparts.hostname,
'all', "all",
] ]
proxy = None proxy = None
for proxy_key in proxy_keys: for proxy_key in proxy_keys:
@@ -844,13 +868,13 @@ def resolve_proxies(request, proxies, trust_env=True):
proxies = proxies if proxies is not None else {} proxies = proxies if proxies is not None else {}
url = request.url url = request.url
scheme = urlparse(url).scheme scheme = urlparse(url).scheme
no_proxy = proxies.get('no_proxy') no_proxy = proxies.get("no_proxy")
new_proxies = proxies.copy() new_proxies = proxies.copy()
if trust_env and not should_bypass_proxies(url, no_proxy=no_proxy): if trust_env and not should_bypass_proxies(url, no_proxy=no_proxy):
environ_proxies = get_environ_proxies(url, no_proxy=no_proxy) environ_proxies = get_environ_proxies(url, no_proxy=no_proxy)
proxy = environ_proxies.get(scheme, environ_proxies.get('all')) proxy = environ_proxies.get(scheme, environ_proxies.get("all"))
if proxy: if proxy:
new_proxies.setdefault(scheme, proxy) new_proxies.setdefault(scheme, proxy)
@@ -863,19 +887,21 @@ def default_user_agent(name="python-requests"):
:rtype: str :rtype: str
""" """
return '%s/%s' % (name, __version__) return f"{name}/{__version__}"
def default_headers(): def default_headers():
""" """
:rtype: requests.structures.CaseInsensitiveDict :rtype: requests.structures.CaseInsensitiveDict
""" """
return CaseInsensitiveDict({ return CaseInsensitiveDict(
'User-Agent': default_user_agent(), {
'Accept-Encoding': DEFAULT_ACCEPT_ENCODING, "User-Agent": default_user_agent(),
'Accept': '*/*', "Accept-Encoding": DEFAULT_ACCEPT_ENCODING,
'Connection': 'keep-alive', "Accept": "*/*",
}) "Connection": "keep-alive",
}
)
def parse_header_links(value): def parse_header_links(value):
@@ -888,23 +914,23 @@ def parse_header_links(value):
links = [] links = []
replace_chars = ' \'"' replace_chars = " '\""
value = value.strip(replace_chars) value = value.strip(replace_chars)
if not value: if not value:
return links return links
for val in re.split(', *<', value): for val in re.split(", *<", value):
try: try:
url, params = val.split(';', 1) url, params = val.split(";", 1)
except ValueError: except ValueError:
url, params = val, '' url, params = val, ""
link = {'url': url.strip('<> \'"')} link = {"url": url.strip("<> '\"")}
for param in params.split(';'): for param in params.split(";"):
try: try:
key, value = param.split('=') key, value = param.split("=")
except ValueError: except ValueError:
break break
@@ -916,7 +942,7 @@ def parse_header_links(value):
# Null bytes; no need to recreate these on each call to guess_json_utf # Null bytes; no need to recreate these on each call to guess_json_utf
_null = '\x00'.encode('ascii') # encoding to ASCII for Python 3 _null = "\x00".encode("ascii") # encoding to ASCII for Python 3
_null2 = _null * 2 _null2 = _null * 2
_null3 = _null * 3 _null3 = _null * 3
@@ -930,25 +956,25 @@ def guess_json_utf(data):
# determine the encoding. Also detect a BOM, if present. # determine the encoding. Also detect a BOM, if present.
sample = data[:4] sample = data[:4]
if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE): if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE):
return 'utf-32' # BOM included return "utf-32" # BOM included
if sample[:3] == codecs.BOM_UTF8: if sample[:3] == codecs.BOM_UTF8:
return 'utf-8-sig' # BOM included, MS style (discouraged) return "utf-8-sig" # BOM included, MS style (discouraged)
if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE): if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE):
return 'utf-16' # BOM included return "utf-16" # BOM included
nullcount = sample.count(_null) nullcount = sample.count(_null)
if nullcount == 0: if nullcount == 0:
return 'utf-8' return "utf-8"
if nullcount == 2: if nullcount == 2:
if sample[::2] == _null2: # 1st and 3rd are null if sample[::2] == _null2: # 1st and 3rd are null
return 'utf-16-be' return "utf-16-be"
if sample[1::2] == _null2: # 2nd and 4th are null if sample[1::2] == _null2: # 2nd and 4th are null
return 'utf-16-le' return "utf-16-le"
# Did not detect 2 valid UTF-16 ascii-range characters # Did not detect 2 valid UTF-16 ascii-range characters
if nullcount == 3: if nullcount == 3:
if sample[:3] == _null3: if sample[:3] == _null3:
return 'utf-32-be' return "utf-32-be"
if sample[1:] == _null3: if sample[1:] == _null3:
return 'utf-32-le' return "utf-32-le"
# Did not detect a valid UTF-32 ascii-range character # Did not detect a valid UTF-32 ascii-range character
return None return None
@@ -973,13 +999,13 @@ def prepend_scheme_if_needed(url, new_scheme):
if auth: if auth:
# parse_url doesn't provide the netloc with auth # parse_url doesn't provide the netloc with auth
# so we'll add it ourselves. # so we'll add it ourselves.
netloc = '@'.join([auth, netloc]) netloc = "@".join([auth, netloc])
if scheme is None: if scheme is None:
scheme = new_scheme scheme = new_scheme
if path is None: if path is None:
path = '' path = ""
return urlunparse((scheme, netloc, path, '', query, fragment)) return urlunparse((scheme, netloc, path, "", query, fragment))
def get_auth_from_url(url): def get_auth_from_url(url):
@@ -993,14 +1019,14 @@ def get_auth_from_url(url):
try: try:
auth = (unquote(parsed.username), unquote(parsed.password)) auth = (unquote(parsed.username), unquote(parsed.password))
except (AttributeError, TypeError): except (AttributeError, TypeError):
auth = ('', '') auth = ("", "")
return auth return auth
# Moved outside of function to avoid recompile every call # Moved outside of function to avoid recompile every call
_CLEAN_HEADER_REGEX_BYTE = re.compile(b'^\\S[^\\r\\n]*$|^$') _CLEAN_HEADER_REGEX_BYTE = re.compile(b"^\\S[^\\r\\n]*$|^$")
_CLEAN_HEADER_REGEX_STR = re.compile(r'^\S[^\r\n]*$|^$') _CLEAN_HEADER_REGEX_STR = re.compile(r"^\S[^\r\n]*$|^$")
def check_header_validity(header): def check_header_validity(header):
@@ -1018,10 +1044,14 @@ def check_header_validity(header):
pat = _CLEAN_HEADER_REGEX_STR pat = _CLEAN_HEADER_REGEX_STR
try: try:
if not pat.match(value): if not pat.match(value):
raise InvalidHeader("Invalid return character or leading space in header: %s" % name) raise InvalidHeader(
f"Invalid return character or leading space in header: {name}"
)
except TypeError: except TypeError:
raise InvalidHeader("Value for header {%s: %s} must be of type str or " raise InvalidHeader(
"bytes, not %s" % (name, value, type(value))) f"Value for header {{{name}: {value}}} must be of type "
f"str or bytes, not {type(value)}"
)
def urldefragauth(url): def urldefragauth(url):
@@ -1036,21 +1066,24 @@ def urldefragauth(url):
if not netloc: if not netloc:
netloc, path = path, netloc netloc, path = path, netloc
netloc = netloc.rsplit('@', 1)[-1] netloc = netloc.rsplit("@", 1)[-1]
return urlunparse((scheme, netloc, path, params, query, '')) return urlunparse((scheme, netloc, path, params, query, ""))
def rewind_body(prepared_request): def rewind_body(prepared_request):
"""Move file pointer back to its recorded starting position """Move file pointer back to its recorded starting position
so it can be read again on redirect. so it can be read again on redirect.
""" """
body_seek = getattr(prepared_request.body, 'seek', None) body_seek = getattr(prepared_request.body, "seek", None)
if body_seek is not None and isinstance(prepared_request._body_position, integer_types): if body_seek is not None and isinstance(
prepared_request._body_position, integer_types
):
try: try:
body_seek(prepared_request._body_position) body_seek(prepared_request._body_position)
except (IOError, OSError): except OSError:
raise UnrewindableBodyError("An error occurred when rewinding request " raise UnrewindableBodyError(
"body for redirect.") "An error occurred when rewinding request body for redirect."
)
else: else:
raise UnrewindableBodyError("Unable to rewind request body for redirect.") raise UnrewindableBodyError("Unable to rewind request body for redirect.")
+7
View File
@@ -8,3 +8,10 @@ requires-dist =
charset_normalizer~=2.0.0 charset_normalizer~=2.0.0
idna>=2.5,<4 idna>=2.5,<4
urllib3>=1.21.1,<1.27 urllib3>=1.21.1,<1.27
[flake8]
ignore = E203, E501, W503
per-file-ignores =
requests/__init__.py:E402, F401
requests/compat.py:E402, F401
tests/compat.py:F401
+56 -55
View File
@@ -1,13 +1,11 @@
#!/usr/bin/env python #!/usr/bin/env python
import os import os
import sys import sys
from codecs import open from codecs import open
from setuptools import setup from setuptools import setup
from setuptools.command.test import test as TestCommand from setuptools.command.test import test as TestCommand
CURRENT_PYTHON = sys.version_info[:2] CURRENT_PYTHON = sys.version_info[:2]
REQUIRED_PYTHON = (3, 7) REQUIRED_PYTHON = (3, 7)
@@ -29,16 +27,18 @@ pin to an older version of Requests (<2.28).
) )
sys.exit(1) sys.exit(1)
class PyTest(TestCommand): class PyTest(TestCommand):
user_options = [('pytest-args=', 'a', "Arguments to pass into py.test")] user_options = [("pytest-args=", "a", "Arguments to pass into py.test")]
def initialize_options(self): def initialize_options(self):
TestCommand.initialize_options(self) TestCommand.initialize_options(self)
try: try:
from multiprocessing import cpu_count from multiprocessing import cpu_count
self.pytest_args = ['-n', str(cpu_count()), '--boxed']
self.pytest_args = ["-n", str(cpu_count()), "--boxed"]
except (ImportError, NotImplementedError): except (ImportError, NotImplementedError):
self.pytest_args = ['-n', '1', '--boxed'] self.pytest_args = ["-n", "1", "--boxed"]
def finalize_options(self): def finalize_options(self):
TestCommand.finalize_options(self) TestCommand.finalize_options(self)
@@ -51,81 +51,82 @@ class PyTest(TestCommand):
errno = pytest.main(self.pytest_args) errno = pytest.main(self.pytest_args)
sys.exit(errno) sys.exit(errno)
# 'setup.py publish' shortcut. # 'setup.py publish' shortcut.
if sys.argv[-1] == 'publish': if sys.argv[-1] == "publish":
os.system('python setup.py sdist bdist_wheel') os.system("python setup.py sdist bdist_wheel")
os.system('twine upload dist/*') os.system("twine upload dist/*")
sys.exit() sys.exit()
requires = [ requires = [
'charset_normalizer~=2.0.0', "charset_normalizer~=2.0.0",
'idna>=2.5,<4', "idna>=2.5,<4",
'urllib3>=1.21.1,<1.27', "urllib3>=1.21.1,<1.27",
'certifi>=2017.4.17', "certifi>=2017.4.17",
] ]
test_requirements = [ test_requirements = [
'pytest-httpbin==0.0.7', "pytest-httpbin==0.0.7",
'pytest-cov', "pytest-cov",
'pytest-mock', "pytest-mock",
'pytest-xdist', "pytest-xdist",
'PySocks>=1.5.6, !=1.5.7', "PySocks>=1.5.6, !=1.5.7",
'pytest>=3', "pytest>=3",
] ]
about = {} about = {}
here = os.path.abspath(os.path.dirname(__file__)) here = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(here, 'requests', '__version__.py'), 'r', 'utf-8') as f: with open(os.path.join(here, "requests", "__version__.py"), "r", "utf-8") as f:
exec(f.read(), about) exec(f.read(), about)
with open('README.md', 'r', 'utf-8') as f: with open("README.md", "r", "utf-8") as f:
readme = f.read() readme = f.read()
setup( setup(
name=about['__title__'], name=about["__title__"],
version=about['__version__'], version=about["__version__"],
description=about['__description__'], description=about["__description__"],
long_description=readme, long_description=readme,
long_description_content_type='text/markdown', long_description_content_type="text/markdown",
author=about['__author__'], author=about["__author__"],
author_email=about['__author_email__'], author_email=about["__author_email__"],
url=about['__url__'], url=about["__url__"],
packages=['requests'], packages=["requests"],
package_data={'': ['LICENSE', 'NOTICE']}, package_data={"": ["LICENSE", "NOTICE"]},
package_dir={'requests': 'requests'}, package_dir={"requests": "requests"},
include_package_data=True, include_package_data=True,
python_requires=">=3.7, <4", python_requires=">=3.7, <4",
install_requires=requires, install_requires=requires,
license=about['__license__'], license=about["__license__"],
zip_safe=False, zip_safe=False,
classifiers=[ classifiers=[
'Development Status :: 5 - Production/Stable', "Development Status :: 5 - Production/Stable",
'Environment :: Web Environment', "Environment :: Web Environment",
'Intended Audience :: Developers', "Intended Audience :: Developers",
'License :: OSI Approved :: Apache Software License', "License :: OSI Approved :: Apache Software License",
'Natural Language :: English', "Natural Language :: English",
'Operating System :: OS Independent', "Operating System :: OS Independent",
'Programming Language :: Python', "Programming Language :: Python",
'Programming Language :: Python :: 3', "Programming Language :: Python :: 3",
'Programming Language :: Python :: 3.7', "Programming Language :: Python :: 3.7",
'Programming Language :: Python :: 3.8', "Programming Language :: Python :: 3.8",
'Programming Language :: Python :: 3.9', "Programming Language :: Python :: 3.9",
'Programming Language :: Python :: 3.10', "Programming Language :: Python :: 3.10",
'Programming Language :: Python :: 3.11', "Programming Language :: Python :: 3.11",
'Programming Language :: Python :: 3 :: Only', "Programming Language :: Python :: 3 :: Only",
'Programming Language :: Python :: Implementation :: CPython', "Programming Language :: Python :: Implementation :: CPython",
'Programming Language :: Python :: Implementation :: PyPy', "Programming Language :: Python :: Implementation :: PyPy",
'Topic :: Internet :: WWW/HTTP', "Topic :: Internet :: WWW/HTTP",
'Topic :: Software Development :: Libraries', "Topic :: Software Development :: Libraries",
], ],
cmdclass={'test': PyTest}, cmdclass={"test": PyTest},
tests_require=test_requirements, tests_require=test_requirements,
extras_require={ extras_require={
'security': [], "security": [],
'socks': ['PySocks>=1.5.6, !=1.5.7'], "socks": ["PySocks>=1.5.6, !=1.5.7"],
'use_chardet_on_py3': ['chardet>=3.0.2,<5'] "use_chardet_on_py3": ["chardet>=3.0.2,<5"],
}, },
project_urls={ project_urls={
'Documentation': 'https://requests.readthedocs.io', "Documentation": "https://requests.readthedocs.io",
'Source': 'https://github.com/psf/requests', "Source": "https://github.com/psf/requests",
}, },
) )
+1 -4
View File
@@ -1,13 +1,10 @@
# -*- coding: utf-8 -*-
"""Requests test package initialisation.""" """Requests test package initialisation."""
import warnings import warnings
import urllib3
from urllib3.exceptions import SNIMissingWarning from urllib3.exceptions import SNIMissingWarning
# urllib3 sets SNIMissingWarning to only go off once, # urllib3 sets SNIMissingWarning to only go off once,
# while this test suite requires it to always fire # while this test suite requires it to always fire
# so that it occurs during test_requests.test_https_warnings # so that it occurs during test_requests.test_https_warnings
warnings.simplefilter('always', SNIMissingWarning) warnings.simplefilter("always", SNIMissingWarning)
-1
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import warnings import warnings
try: try:
+6 -9
View File
@@ -1,26 +1,23 @@
# -*- coding: utf-8 -*-
try: try:
from http.server import HTTPServer from http.server import HTTPServer, SimpleHTTPRequestHandler
from http.server import SimpleHTTPRequestHandler
except ImportError: except ImportError:
from BaseHTTPServer import HTTPServer from BaseHTTPServer import HTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler from SimpleHTTPServer import SimpleHTTPRequestHandler
import ssl import ssl
import tempfile
import threading import threading
import pytest import pytest
from requests.compat import urljoin from requests.compat import urljoin
def prepare_url(value): def prepare_url(value):
# Issue #1483: Make sure the URL always has a trailing slash # Issue #1483: Make sure the URL always has a trailing slash
httpbin_url = value.url.rstrip('/') + '/' httpbin_url = value.url.rstrip("/") + "/"
def inner(*suffix): def inner(*suffix):
return urljoin(httpbin_url, '/'.join(suffix)) return urljoin(httpbin_url, "/".join(suffix))
return inner return inner
@@ -44,7 +41,7 @@ def nosan_server(tmp_path_factory):
tmpdir = tmp_path_factory.mktemp("certs") tmpdir = tmp_path_factory.mktemp("certs")
ca = trustme.CA() ca = trustme.CA()
# only commonName, no subjectAltName # only commonName, no subjectAltName
server_cert = ca.issue_cert(common_name=u"localhost") server_cert = ca.issue_cert(common_name="localhost")
ca_bundle = str(tmpdir / "ca.pem") ca_bundle = str(tmpdir / "ca.pem")
ca.cert_pem.write_to_path(ca_bundle) ca.cert_pem.write_to_path(ca_bundle)
+6 -12
View File
@@ -1,18 +1,12 @@
# -*- encoding: utf-8
import sys
import pytest
from requests.help import info from requests.help import info
def test_system_ssl(): def test_system_ssl():
"""Verify we're actually setting system_ssl when it should be available.""" """Verify we're actually setting system_ssl when it should be available."""
assert info()['system_ssl']['version'] != '' assert info()["system_ssl"]["version"] != ""
class VersionedPackage(object): class VersionedPackage:
def __init__(self, version): def __init__(self, version):
self.__version__ = version self.__version__ = version
@@ -21,11 +15,11 @@ def test_idna_without_version_attribute(mocker):
"""Older versions of IDNA don't provide a __version__ attribute, verify """Older versions of IDNA don't provide a __version__ attribute, verify
that if we have such a package, we don't blow up. that if we have such a package, we don't blow up.
""" """
mocker.patch('requests.help.idna', new=None) mocker.patch("requests.help.idna", new=None)
assert info()['idna'] == {'version': ''} assert info()["idna"] == {"version": ""}
def test_idna_with_version_attribute(mocker): def test_idna_with_version_attribute(mocker):
"""Verify we're actually setting idna version when it should be available.""" """Verify we're actually setting idna version when it should be available."""
mocker.patch('requests.help.idna', new=VersionedPackage('2.6')) mocker.patch("requests.help.idna", new=VersionedPackage("2.6"))
assert info()['idna'] == {'version': '2.6'} assert info()["idna"] == {"version": "2.6"}
+7 -8
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import pytest import pytest
from requests import hooks from requests import hooks
@@ -10,14 +8,15 @@ def hook(value):
@pytest.mark.parametrize( @pytest.mark.parametrize(
'hooks_list, result', ( "hooks_list, result",
(hook, 'ata'), (
([hook, lambda x: None, hook], 'ta'), (hook, "ata"),
) ([hook, lambda x: None, hook], "ta"),
),
) )
def test_hooks(hooks_list, result): def test_hooks(hooks_list, result):
assert hooks.dispatch_hook('response', {'response': hooks_list}, 'Data') == result assert hooks.dispatch_hook("response", {"response": hooks_list}, "Data") == result
def test_default_hooks(): def test_default_hooks():
assert hooks.default_hooks() == {'response': []} assert hooks.default_hooks() == {"response": []}
+61 -62
View File
@@ -1,12 +1,11 @@
# -*- coding: utf-8 -*- import threading
import pytest import pytest
import threading
import requests
from tests.testserver.server import Server, consume_socket_content from tests.testserver.server import Server, consume_socket_content
import requests
from requests.compat import JSONDecodeError from requests.compat import JSONDecodeError
from .utils import override_environ from .utils import override_environ
@@ -15,9 +14,9 @@ def echo_response_handler(sock):
request_content = consume_socket_content(sock, timeout=0.5) request_content = consume_socket_content(sock, timeout=0.5)
text_200 = ( text_200 = (
b'HTTP/1.1 200 OK\r\n' b"HTTP/1.1 200 OK\r\n"
b'Content-Length: %d\r\n\r\n' b"Content-Length: %d\r\n\r\n"
b'%s' b"%s"
) % (len(request_content), request_content) ) % (len(request_content), request_content)
sock.send(text_200) sock.send(text_200)
@@ -26,15 +25,15 @@ def test_chunked_upload():
"""can safely send generators""" """can safely send generators"""
close_server = threading.Event() close_server = threading.Event()
server = Server.basic_response_server(wait_to_close_event=close_server) server = Server.basic_response_server(wait_to_close_event=close_server)
data = iter([b'a', b'b', b'c']) data = iter([b"a", b"b", b"c"])
with server as (host, port): with server as (host, port):
url = 'http://{}:{}/'.format(host, port) url = f"http://{host}:{port}/"
r = requests.post(url, data=data, stream=True) r = requests.post(url, data=data, stream=True)
close_server.set() # release server block close_server.set() # release server block
assert r.status_code == 200 assert r.status_code == 200
assert r.request.headers['Transfer-Encoding'] == 'chunked' assert r.request.headers["Transfer-Encoding"] == "chunked"
def test_chunked_encoding_error(): def test_chunked_encoding_error():
@@ -44,8 +43,10 @@ def test_chunked_encoding_error():
request_content = consume_socket_content(sock, timeout=0.5) request_content = consume_socket_content(sock, timeout=0.5)
# The server never ends the request and doesn't provide any valid chunks # The server never ends the request and doesn't provide any valid chunks
sock.send(b"HTTP/1.1 200 OK\r\n" + sock.send(
b"Transfer-Encoding: chunked\r\n") b"HTTP/1.1 200 OK\r\n"
b"Transfer-Encoding: chunked\r\n"
)
return request_content return request_content
@@ -53,9 +54,9 @@ def test_chunked_encoding_error():
server = Server(incomplete_chunked_response_handler) server = Server(incomplete_chunked_response_handler)
with server as (host, port): with server as (host, port):
url = 'http://{}:{}/'.format(host, port) url = f"http://{host}:{port}/"
with pytest.raises(requests.exceptions.ChunkedEncodingError): with pytest.raises(requests.exceptions.ChunkedEncodingError):
r = requests.get(url) requests.get(url)
close_server.set() # release server block close_server.set() # release server block
@@ -64,17 +65,17 @@ def test_chunked_upload_uses_only_specified_host_header():
close_server = threading.Event() close_server = threading.Event()
server = Server(echo_response_handler, wait_to_close_event=close_server) server = Server(echo_response_handler, wait_to_close_event=close_server)
data = iter([b'a', b'b', b'c']) data = iter([b"a", b"b", b"c"])
custom_host = 'sample-host' custom_host = "sample-host"
with server as (host, port): with server as (host, port):
url = 'http://{}:{}/'.format(host, port) url = f"http://{host}:{port}/"
r = requests.post(url, data=data, headers={'Host': custom_host}, stream=True) r = requests.post(url, data=data, headers={"Host": custom_host}, stream=True)
close_server.set() # release server block close_server.set() # release server block
expected_header = b'Host: %s\r\n' % custom_host.encode('utf-8') expected_header = b"Host: %s\r\n" % custom_host.encode("utf-8")
assert expected_header in r.content assert expected_header in r.content
assert r.content.count(b'Host: ') == 1 assert r.content.count(b"Host: ") == 1
def test_chunked_upload_doesnt_skip_host_header(): def test_chunked_upload_doesnt_skip_host_header():
@@ -82,17 +83,17 @@ def test_chunked_upload_doesnt_skip_host_header():
close_server = threading.Event() close_server = threading.Event()
server = Server(echo_response_handler, wait_to_close_event=close_server) server = Server(echo_response_handler, wait_to_close_event=close_server)
data = iter([b'a', b'b', b'c']) data = iter([b"a", b"b", b"c"])
with server as (host, port): with server as (host, port):
expected_host = '{}:{}'.format(host, port) expected_host = f"{host}:{port}"
url = 'http://{}:{}/'.format(host, port) url = f"http://{host}:{port}/"
r = requests.post(url, data=data, stream=True) r = requests.post(url, data=data, stream=True)
close_server.set() # release server block close_server.set() # release server block
expected_header = b'Host: %s\r\n' % expected_host.encode('utf-8') expected_header = b"Host: %s\r\n" % expected_host.encode("utf-8")
assert expected_header in r.content assert expected_header in r.content
assert r.content.count(b'Host: ') == 1 assert r.content.count(b"Host: ") == 1
def test_conflicting_content_lengths(): def test_conflicting_content_lengths():
@@ -102,12 +103,14 @@ def test_conflicting_content_lengths():
def multiple_content_length_response_handler(sock): def multiple_content_length_response_handler(sock):
request_content = consume_socket_content(sock, timeout=0.5) request_content = consume_socket_content(sock, timeout=0.5)
response = (
sock.send(b"HTTP/1.1 200 OK\r\n" + b"HTTP/1.1 200 OK\r\n"
b"Content-Type: text/plain\r\n" + b"Content-Type: text/plain\r\n"
b"Content-Length: 16\r\n" + b"Content-Length: 16\r\n"
b"Content-Length: 32\r\n\r\n" + b"Content-Length: 32\r\n\r\n"
b"-- Bad Actor -- Original Content\r\n") b"-- Bad Actor -- Original Content\r\n"
)
sock.send(response)
return request_content return request_content
@@ -115,9 +118,9 @@ def test_conflicting_content_lengths():
server = Server(multiple_content_length_response_handler) server = Server(multiple_content_length_response_handler)
with server as (host, port): with server as (host, port):
url = 'http://{}:{}/'.format(host, port) url = f"http://{host}:{port}/"
with pytest.raises(requests.exceptions.InvalidHeader): with pytest.raises(requests.exceptions.InvalidHeader):
r = requests.get(url) requests.get(url)
close_server.set() close_server.set()
@@ -174,7 +177,7 @@ def test_digestauth_401_count_reset_on_redirect():
server = Server(digest_response_handler, wait_to_close_event=close_server) server = Server(digest_response_handler, wait_to_close_event=close_server)
with server as (host, port): with server as (host, port):
url = 'http://{}:{}/'.format(host, port) url = f'http://{host}:{port}/'
r = requests.get(url, auth=auth) r = requests.get(url, auth=auth)
# Verify server succeeded in authenticating. # Verify server succeeded in authenticating.
assert r.status_code == 200 assert r.status_code == 200
@@ -224,7 +227,7 @@ def test_digestauth_401_only_sent_once():
server = Server(digest_failed_response_handler, wait_to_close_event=close_server) server = Server(digest_failed_response_handler, wait_to_close_event=close_server)
with server as (host, port): with server as (host, port):
url = 'http://{}:{}/'.format(host, port) url = f'http://{host}:{port}/'
r = requests.get(url, auth=auth) r = requests.get(url, auth=auth)
# Verify server didn't authenticate us. # Verify server didn't authenticate us.
assert r.status_code == 401 assert r.status_code == 401
@@ -261,7 +264,7 @@ def test_digestauth_only_on_4xx():
server = Server(digest_response_handler, wait_to_close_event=close_server) server = Server(digest_response_handler, wait_to_close_event=close_server)
with server as (host, port): with server as (host, port):
url = 'http://{}:{}/'.format(host, port) url = f'http://{host}:{port}/'
r = requests.get(url, auth=auth) r = requests.get(url, auth=auth)
# Verify server didn't receive auth from us. # Verify server didn't receive auth from us.
assert r.status_code == 200 assert r.status_code == 200
@@ -278,17 +281,17 @@ _schemes_by_var_prefix = [
_proxy_combos = [] _proxy_combos = []
for prefix, schemes in _schemes_by_var_prefix: for prefix, schemes in _schemes_by_var_prefix:
for scheme in schemes: for scheme in schemes:
_proxy_combos.append(("{}_proxy".format(prefix), scheme)) _proxy_combos.append((f"{prefix}_proxy", scheme))
_proxy_combos += [(var.upper(), scheme) for var, scheme in _proxy_combos] _proxy_combos += [(var.upper(), scheme) for var, scheme in _proxy_combos]
@pytest.mark.parametrize("var,scheme", _proxy_combos) @pytest.mark.parametrize("var,scheme", _proxy_combos)
def test_use_proxy_from_environment(httpbin, var, scheme): def test_use_proxy_from_environment(httpbin, var, scheme):
url = "{}://httpbin.org".format(scheme) url = f"{scheme}://httpbin.org"
fake_proxy = Server() # do nothing with the requests; just close the socket fake_proxy = Server() # do nothing with the requests; just close the socket
with fake_proxy as (host, port): with fake_proxy as (host, port):
proxy_url = "socks5://{}:{}".format(host, port) proxy_url = f"socks5://{host}:{port}"
kwargs = {var: proxy_url} kwargs = {var: proxy_url}
with override_environ(**kwargs): with override_environ(**kwargs):
# fake proxy's lack of response will cause a ConnectionError # fake proxy's lack of response will cause a ConnectionError
@@ -303,18 +306,20 @@ def test_use_proxy_from_environment(httpbin, var, scheme):
def test_redirect_rfc1808_to_non_ascii_location(): def test_redirect_rfc1808_to_non_ascii_location():
path = u'š' path = 'š'
expected_path = b'%C5%A1' expected_path = b'%C5%A1'
redirect_request = [] # stores the second request to the server redirect_request = [] # stores the second request to the server
def redirect_resp_handler(sock): def redirect_resp_handler(sock):
consume_socket_content(sock, timeout=0.5) consume_socket_content(sock, timeout=0.5)
location = u'//{}:{}/{}'.format(host, port, path) location = f'//{host}:{port}/{path}'
sock.send( sock.send(
b'HTTP/1.1 301 Moved Permanently\r\n' (
b'Content-Length: 0\r\n' b'HTTP/1.1 301 Moved Permanently\r\n'
b'Location: ' + location.encode('utf8') + b'\r\n' b'Content-Length: 0\r\n'
b'\r\n' b'Location: %s\r\n'
b'\r\n'
) % location.encode('utf8')
) )
redirect_request.append(consume_socket_content(sock, timeout=0.5)) redirect_request.append(consume_socket_content(sock, timeout=0.5))
sock.send(b'HTTP/1.1 200 OK\r\n\r\n') sock.send(b'HTTP/1.1 200 OK\r\n\r\n')
@@ -323,31 +328,24 @@ def test_redirect_rfc1808_to_non_ascii_location():
server = Server(redirect_resp_handler, wait_to_close_event=close_server) server = Server(redirect_resp_handler, wait_to_close_event=close_server)
with server as (host, port): with server as (host, port):
url = u'http://{}:{}'.format(host, port) url = f'http://{host}:{port}'
r = requests.get(url=url, allow_redirects=True) r = requests.get(url=url, allow_redirects=True)
assert r.status_code == 200 assert r.status_code == 200
assert len(r.history) == 1 assert len(r.history) == 1
assert r.history[0].status_code == 301 assert r.history[0].status_code == 301
assert redirect_request[0].startswith(b'GET /' + expected_path + b' HTTP/1.1') assert redirect_request[0].startswith(b'GET /' + expected_path + b' HTTP/1.1')
assert r.url == u'{}/{}'.format(url, expected_path.decode('ascii')) assert r.url == '{}/{}'.format(url, expected_path.decode('ascii'))
close_server.set() close_server.set()
def test_fragment_not_sent_with_request(): def test_fragment_not_sent_with_request():
"""Verify that the fragment portion of a URI isn't sent to the server.""" """Verify that the fragment portion of a URI isn't sent to the server."""
def response_handler(sock):
req = consume_socket_content(sock, timeout=0.5)
sock.send(
b'HTTP/1.1 200 OK\r\n'
b'Content-Length: '+bytes(len(req))+b'\r\n'
b'\r\n'+req
)
close_server = threading.Event() close_server = threading.Event()
server = Server(response_handler, wait_to_close_event=close_server) server = Server(echo_response_handler, wait_to_close_event=close_server)
with server as (host, port): with server as (host, port):
url = 'http://{}:{}/path/to/thing/#view=edit&token=hunter2'.format(host, port) url = f'http://{host}:{port}/path/to/thing/#view=edit&token=hunter2'
r = requests.get(url) r = requests.get(url)
raw_request = r.content raw_request = r.content
@@ -362,6 +360,7 @@ def test_fragment_not_sent_with_request():
close_server.set() close_server.set()
def test_fragment_update_on_redirect(): def test_fragment_update_on_redirect():
"""Verify we only append previous fragment if one doesn't exist on new """Verify we only append previous fragment if one doesn't exist on new
location. If a new fragment is encountered in a Location header, it should location. If a new fragment is encountered in a Location header, it should
@@ -390,21 +389,21 @@ def test_fragment_update_on_redirect():
server = Server(response_handler, wait_to_close_event=close_server) server = Server(response_handler, wait_to_close_event=close_server)
with server as (host, port): with server as (host, port):
url = 'http://{}:{}/path/to/thing/#view=edit&token=hunter2'.format(host, port) url = f'http://{host}:{port}/path/to/thing/#view=edit&token=hunter2'
r = requests.get(url) r = requests.get(url)
raw_request = r.content
assert r.status_code == 200 assert r.status_code == 200
assert len(r.history) == 2 assert len(r.history) == 2
assert r.history[0].request.url == url assert r.history[0].request.url == url
# Verify we haven't overwritten the location with our previous fragment. # Verify we haven't overwritten the location with our previous fragment.
assert r.history[1].request.url == 'http://{}:{}/get#relevant-section'.format(host, port) assert r.history[1].request.url == f'http://{host}:{port}/get#relevant-section'
# Verify previous fragment is used and not the original. # Verify previous fragment is used and not the original.
assert r.url == 'http://{}:{}/final-url/#relevant-section'.format(host, port) assert r.url == f'http://{host}:{port}/final-url/#relevant-section'
close_server.set() close_server.set()
def test_json_decode_compatibility_for_alt_utf_encodings(): def test_json_decode_compatibility_for_alt_utf_encodings():
def response_handler(sock): def response_handler(sock):
@@ -419,7 +418,7 @@ def test_json_decode_compatibility_for_alt_utf_encodings():
server = Server(response_handler, wait_to_close_event=close_server) server = Server(response_handler, wait_to_close_event=close_server)
with server as (host, port): with server as (host, port):
url = 'http://{}:{}/'.format(host, port) url = f'http://{host}:{port}/'
r = requests.get(url) r = requests.get(url)
r.encoding = None r.encoding = None
with pytest.raises(requests.exceptions.JSONDecodeError) as excinfo: with pytest.raises(requests.exceptions.JSONDecodeError) as excinfo:
+984 -900
View File
File diff suppressed because it is too large Load Diff
+20 -18
View File
@@ -1,26 +1,25 @@
# -*- coding: utf-8 -*-
import pytest import pytest
from requests.structures import CaseInsensitiveDict, LookupDict from requests.structures import CaseInsensitiveDict, LookupDict
class TestCaseInsensitiveDict: class TestCaseInsensitiveDict:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def setup(self): def setup(self):
"""CaseInsensitiveDict instance with "Accept" header.""" """CaseInsensitiveDict instance with "Accept" header."""
self.case_insensitive_dict = CaseInsensitiveDict() self.case_insensitive_dict = CaseInsensitiveDict()
self.case_insensitive_dict['Accept'] = 'application/json' self.case_insensitive_dict["Accept"] = "application/json"
def test_list(self): def test_list(self):
assert list(self.case_insensitive_dict) == ['Accept'] assert list(self.case_insensitive_dict) == ["Accept"]
possible_keys = pytest.mark.parametrize('key', ('accept', 'ACCEPT', 'aCcEpT', 'Accept')) possible_keys = pytest.mark.parametrize(
"key", ("accept", "ACCEPT", "aCcEpT", "Accept")
)
@possible_keys @possible_keys
def test_getitem(self, key): def test_getitem(self, key):
assert self.case_insensitive_dict[key] == 'application/json' assert self.case_insensitive_dict[key] == "application/json"
@possible_keys @possible_keys
def test_delitem(self, key): def test_delitem(self, key):
@@ -28,7 +27,9 @@ class TestCaseInsensitiveDict:
assert key not in self.case_insensitive_dict assert key not in self.case_insensitive_dict
def test_lower_items(self): def test_lower_items(self):
assert list(self.case_insensitive_dict.lower_items()) == [('accept', 'application/json')] assert list(self.case_insensitive_dict.lower_items()) == [
("accept", "application/json")
]
def test_repr(self): def test_repr(self):
assert repr(self.case_insensitive_dict) == "{'Accept': 'application/json'}" assert repr(self.case_insensitive_dict) == "{'Accept': 'application/json'}"
@@ -39,32 +40,33 @@ class TestCaseInsensitiveDict:
assert copy == self.case_insensitive_dict assert copy == self.case_insensitive_dict
@pytest.mark.parametrize( @pytest.mark.parametrize(
'other, result', ( "other, result",
({'AccePT': 'application/json'}, True), (
({"AccePT": "application/json"}, True),
({}, False), ({}, False),
(None, False) (None, False),
) ),
) )
def test_instance_equality(self, other, result): def test_instance_equality(self, other, result):
assert (self.case_insensitive_dict == other) is result assert (self.case_insensitive_dict == other) is result
class TestLookupDict: class TestLookupDict:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def setup(self): def setup(self):
"""LookupDict instance with "bad_gateway" attribute.""" """LookupDict instance with "bad_gateway" attribute."""
self.lookup_dict = LookupDict('test') self.lookup_dict = LookupDict("test")
self.lookup_dict.bad_gateway = 502 self.lookup_dict.bad_gateway = 502
def test_repr(self): def test_repr(self):
assert repr(self.lookup_dict) == "<lookup 'test'>" assert repr(self.lookup_dict) == "<lookup 'test'>"
get_item_parameters = pytest.mark.parametrize( get_item_parameters = pytest.mark.parametrize(
'key, value', ( "key, value",
('bad_gateway', 502), (
('not_a_key', None) ("bad_gateway", 502),
) ("not_a_key", None),
),
) )
@get_item_parameters @get_item_parameters
+22 -23
View File
@@ -1,16 +1,14 @@
# -*- coding: utf-8 -*-
import threading
import socket import socket
import threading
import time import time
import pytest import pytest
import requests
from tests.testserver.server import Server from tests.testserver.server import Server
import requests
class TestTestServer: class TestTestServer:
def test_basic(self): def test_basic(self):
"""messages are sent and received properly""" """messages are sent and received properly"""
question = b"success?" question = b"success?"
@@ -44,36 +42,37 @@ class TestTestServer:
def test_text_response(self): def test_text_response(self):
"""the text_response_server sends the given text""" """the text_response_server sends the given text"""
server = Server.text_response_server( server = Server.text_response_server(
"HTTP/1.1 200 OK\r\n" + "HTTP/1.1 200 OK\r\n" "Content-Length: 6\r\n" "\r\nroflol"
"Content-Length: 6\r\n" +
"\r\nroflol"
) )
with server as (host, port): with server as (host, port):
r = requests.get('http://{}:{}'.format(host, port)) r = requests.get(f"http://{host}:{port}")
assert r.status_code == 200 assert r.status_code == 200
assert r.text == u'roflol' assert r.text == "roflol"
assert r.headers['Content-Length'] == '6' assert r.headers["Content-Length"] == "6"
def test_basic_response(self): def test_basic_response(self):
"""the basic response server returns an empty http response""" """the basic response server returns an empty http response"""
with Server.basic_response_server() as (host, port): with Server.basic_response_server() as (host, port):
r = requests.get('http://{}:{}'.format(host, port)) r = requests.get(f"http://{host}:{port}")
assert r.status_code == 200 assert r.status_code == 200
assert r.text == u'' assert r.text == ""
assert r.headers['Content-Length'] == '0' assert r.headers["Content-Length"] == "0"
def test_basic_waiting_server(self): def test_basic_waiting_server(self):
"""the server waits for the block_server event to be set before closing""" """the server waits for the block_server event to be set before closing"""
block_server = threading.Event() block_server = threading.Event()
with Server.basic_response_server(wait_to_close_event=block_server) as (host, port): with Server.basic_response_server(wait_to_close_event=block_server) as (
host,
port,
):
sock = socket.socket() sock = socket.socket()
sock.connect((host, port)) sock.connect((host, port))
sock.sendall(b'send something') sock.sendall(b"send something")
time.sleep(2.5) time.sleep(2.5)
sock.sendall(b'still alive') sock.sendall(b"still alive")
block_server.set() # release server block block_server.set() # release server block
def test_multiple_requests(self): def test_multiple_requests(self):
@@ -83,7 +82,7 @@ class TestTestServer:
server = Server.basic_response_server(requests_to_handle=requests_to_handle) server = Server.basic_response_server(requests_to_handle=requests_to_handle)
with server as (host, port): with server as (host, port):
server_url = 'http://{}:{}'.format(host, port) server_url = f"http://{host}:{port}"
for _ in range(requests_to_handle): for _ in range(requests_to_handle):
r = requests.get(server_url) r = requests.get(server_url)
assert r.status_code == 200 assert r.status_code == 200
@@ -97,8 +96,8 @@ class TestTestServer:
"""can check the requests content""" """can check the requests content"""
# TODO: figure out why this sometimes fails when using pytest-xdist. # TODO: figure out why this sometimes fails when using pytest-xdist.
server = Server.basic_response_server(requests_to_handle=2) server = Server.basic_response_server(requests_to_handle=2)
first_request = b'put your hands up in the air' first_request = b"put your hands up in the air"
second_request = b'put your hand down in the floor' second_request = b"put your hand down in the floor"
with server as address: with server as address:
sock1 = socket.socket() sock1 = socket.socket()
@@ -123,15 +122,15 @@ class TestTestServer:
sock = socket.socket() sock = socket.socket()
sock.connect(address) sock.connect(address)
time.sleep(1.5) time.sleep(1.5)
sock.sendall(b'hehehe, not received') sock.sendall(b"hehehe, not received")
sock.close() sock.close()
assert server.handler_results[0] == b'' assert server.handler_results[0] == b""
def test_request_recovery_with_bigger_timeout(self): def test_request_recovery_with_bigger_timeout(self):
"""a biggest timeout can be specified""" """a biggest timeout can be specified"""
server = Server.basic_response_server(request_timeout=3) server = Server.basic_response_server(request_timeout=3)
data = b'bananadine' data = b"bananadine"
with server as address: with server as address:
sock = socket.socket() sock = socket.socket()
+476 -390
View File
File diff suppressed because it is too large Load Diff
+22 -17
View File
@@ -1,13 +1,11 @@
# -*- coding: utf-8 -*-
import threading
import socket
import select import select
import socket
import threading
def consume_socket_content(sock, timeout=0.5): def consume_socket_content(sock, timeout=0.5):
chunks = 65536 chunks = 65536
content = b'' content = b""
while True: while True:
more_to_read = select.select([sock], [], [], timeout)[0] more_to_read = select.select([sock], [], [], timeout)[0]
@@ -25,10 +23,18 @@ def consume_socket_content(sock, timeout=0.5):
class Server(threading.Thread): class Server(threading.Thread):
"""Dummy server using for unit testing""" """Dummy server using for unit testing"""
WAIT_EVENT_TIMEOUT = 5 WAIT_EVENT_TIMEOUT = 5
def __init__(self, handler=None, host='localhost', port=0, requests_to_handle=1, wait_to_close_event=None): def __init__(
super(Server, self).__init__() self,
handler=None,
host="localhost",
port=0,
requests_to_handle=1,
wait_to_close_event=None,
):
super().__init__()
self.handler = handler or consume_socket_content self.handler = handler or consume_socket_content
self.handler_results = [] self.handler_results = []
@@ -45,19 +51,16 @@ class Server(threading.Thread):
def text_response_server(cls, text, request_timeout=0.5, **kwargs): def text_response_server(cls, text, request_timeout=0.5, **kwargs):
def text_response_handler(sock): def text_response_handler(sock):
request_content = consume_socket_content(sock, timeout=request_timeout) request_content = consume_socket_content(sock, timeout=request_timeout)
sock.send(text.encode('utf-8')) sock.send(text.encode("utf-8"))
return request_content return request_content
return Server(text_response_handler, **kwargs) return Server(text_response_handler, **kwargs)
@classmethod @classmethod
def basic_response_server(cls, **kwargs): def basic_response_server(cls, **kwargs):
return cls.text_response_server( return cls.text_response_server(
"HTTP/1.1 200 OK\r\n" + "HTTP/1.1 200 OK\r\n" + "Content-Length: 0\r\n\r\n", **kwargs
"Content-Length: 0\r\n\r\n",
**kwargs
) )
def run(self): def run(self):
@@ -71,7 +74,7 @@ class Server(threading.Thread):
if self.wait_to_close_event: if self.wait_to_close_event:
self.wait_to_close_event.wait(self.WAIT_EVENT_TIMEOUT) self.wait_to_close_event.wait(self.WAIT_EVENT_TIMEOUT)
finally: finally:
self.ready_event.set() # just in case of exception self.ready_event.set() # just in case of exception
self._close_server_sock_ignore_errors() self._close_server_sock_ignore_errors()
self.stop_event.set() self.stop_event.set()
@@ -84,7 +87,7 @@ class Server(threading.Thread):
def _close_server_sock_ignore_errors(self): def _close_server_sock_ignore_errors(self):
try: try:
self.server_sock.close() self.server_sock.close()
except IOError: except OSError:
pass pass
def _handle_requests(self): def _handle_requests(self):
@@ -100,12 +103,14 @@ class Server(threading.Thread):
def _accept_connection(self): def _accept_connection(self):
try: try:
ready, _, _ = select.select([self.server_sock], [], [], self.WAIT_EVENT_TIMEOUT) ready, _, _ = select.select(
[self.server_sock], [], [], self.WAIT_EVENT_TIMEOUT
)
if not ready: if not ready:
return None return None
return self.server_sock.accept()[0] return self.server_sock.accept()[0]
except (select.error, socket.error): except OSError:
return None return None
def __enter__(self): def __enter__(self):
@@ -125,4 +130,4 @@ class Server(threading.Thread):
# ensure server thread doesn't get stuck waiting for connections # ensure server thread doesn't get stuck waiting for connections
self._close_server_sock_ignore_errors() self._close_server_sock_ignore_errors()
self.join() self.join()
return False # allow exceptions to propagate return False # allow exceptions to propagate
-2
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
import contextlib import contextlib
import os import os