Move CI to [NAME_REDACTED]

Moving us to GH Actions since our Travis configs are in relative disarray and we haven't been running Mac/Windows builds for years at this point.

### Other changes in this PR:

**Pipenv**
Pipenv has caused a lot of friction with users trying to submit PR updates that need dependency changes, so this removes it. I've readded a `requirements-dev.txt` to store dev dependencies.

**Removal of pytest-xdist**
`pytest-xdist` causes threading issues on macOS for some versions of Python [StackOverflow issue](https://stackoverflow.com/questions/50168647/multiprocessing-causes-python-to-crash-and-gives-an-error-may-have-been-in-progr/52230415). The performance gain on tests is negligible, so I've removed it for config simplicity. If there's strong demand, we could attempt the bash updates or split out the macOS test command in the action definition.

**[NAME_REDACTED] 3.9**
Adding 3.9 to tests so we can update our trove classifiers before releasing 2.25.

@@ -0,0 +1,31 @@
+name: Tests
+
+on: [push, pull_request]
+
+jobs:
+  build:
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      fail-fast: false
+      matrix:
+        python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9]
+        os: [ubuntu-latest, macOS-latest, windows-latest]
+        include:
+          # pypy3 on Mac OS currently fails trying to compile
+          # brotlipy. Moving pypy3 to only test linux.
+          - python-version: pypy3
+            os: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v2
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v2
+      with:
+        python-version: ${{ matrix.python-version }}
+    - name: Install dependencies
+      run: |
+        make
+    - name: Run tests
+      run: |
+        make ci

@@ -1,26 +0,0 @@
-language: python
-# command to install dependencies
-install: "make"
-# command to run tests
-script:
-  - make test-readme
-  - make ci
-cache: pip
-jobs:
-  include:
-    - stage: test
-      python: '2.7'
-    - stage: test
-      python: '3.5'
-    - stage: test
-      python: '3.6'
-    - stage: test
-      python: '3.7'
-    - stage: test
-      python: 'pypy3'
-      dist: xenial
-    - stage: test
-      python: '3.8'
-    - stage: coverage
-      python: '3.6'
-      script: codecov

@@ -1,2 +1,2 @@
-include README.md LICENSE NOTICE HISTORY.md pytest.ini requirements.txt [NAME_REDACTED]
+include README.md LICENSE NOTICE HISTORY.md pytest.ini requirements-dev.txt
 recursive-include tests *.py

@@ -1,21 +1,21 @@
 .PHONY: docs
 init:
-	pip install pipenv --upgrade
-	pipenv install --dev
+	pip install -e .[socks]
+	pip install -r requirements-dev.txt
 test:
 	# This runs all of the tests, on both Python 2 and Python 3.
 	detox
 ci:
-	pipenv run py.test -n 8 --boxed --junitxml=report.xml
+	pytest tests --junitxml=report.xml

 test-readme:
-	[USER_REDACTED] run python setup.py check --restructuredtext --strict && ([ $$? -eq 0 ] && echo "README.rst and HISTORY.rst ok") || echo "Invalid markup in README.rst or HISTORY.rst!"
+	python setup.py check --restructuredtext --strict && ([ $$? -eq 0 ] && echo "README.rst and HISTORY.rst ok") || echo "Invalid markup in README.rst or HISTORY.rst!"

 flake8:
-	pipenv run flake8 --ignore=E501,F401,E128,E402,E731,F821 requests
+	flake8 --ignore=E501,F401,E128,E402,E731,F821 requests

 coverage:
-	pipenv run py.test --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=requests tests
+	pytest --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=requests tests

 publish:
 	pip install 'twine>=1.5.0'
@@ -25,4 +25,4 @@ publish:

 docs:
 	cd docs && make html
-	[USER_REDACTED] "\033[95m\n\nBuild successful! View the docs homepage at docs/_build/html/index.html.\n\033[0m"
\ No newline at end of file
+	[USER_REDACTED] "\033[95m\n\nBuild successful! View the docs homepage at docs/_build/html/index.html.\n\033[0m"

@@ -1,25 +0,0 @@
-[[source]]
-url = "https://pypi.org/simple/"
-verify_ssl = true
-name = "pypi"
-
-[dev-packages]
-alabaster = "*"
-codecov = "*"
-docutils = "*"
-detox = "*"
-"flake8" = "*"
-httpbin = ">=0.7.0"
-more-itertools = "<6.0"
-pysocks = "*"
-pytest = ">=2.8.0,<=3.10.1"
-pytest-httpbin = ">=0.0.7,<1.0"
-pytest-mock = "*"
-pytest-cov = "*"
-pytest-xdist = "<=1.25"
-readme-renderer = "*"
-sphinx = "<=1.5.5"
-tox = "*"
-
-[packages]
-"requests" = {path = ".", editable = true, extras = ["socks"]}

@@ -1,229 +0,0 @@
-# Sample script to install Python and pip under Windows
-# Authors: [NAME_REDACTED], [NAME_REDACTED], [NAME_REDACTED], and [NAME_REDACTED]
-# License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
-
-$MINICONDA_URL = "http://repo.continuum.io/miniconda/"
-$BASE_URL = "https://www.python.org/ftp/python/"
-$GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py"
-$GET_PIP_PATH = "C:\get-pip.py"
-
-$PYTHON_PRERELEASE_REGEX = @"
-(?x)
-(?<major>\d+)
-\.
-(?<minor>\d+)
-\.
-(?<micro>\d+)
-(?<prerelease>[a-z]{1,2}\d+)
-"@
-
-
-function Download ($filename, $url) {
-    $webclient = [NAME_REDACTED]
-
-    $basedir = $pwd.Path + "\"
-    $filepath = $basedir + $filename
-    if (Test-Path $filename) {
-        Write-Host "Reusing" $filepath
-        return $filepath
-    }
-
-    # Download and retry up to 3 times in case of network transient errors.
-    Write-Host "Downloading" $filename "from" $url
-    $retry_attempts = 2
-    for ($i = 0; $i -lt $retry_attempts; $i++) {
-        try {
-            $webclient.DownloadFile($url, $filepath)
-            break
-        }
-        Catch [Exception]{
-            Start-Sleep 1
-        }
-    }
-    if (Test-Path $filepath) {
-        Write-Host "File saved at" $filepath
-    } else {
-        # Retry once to get the error message if any at the last try
-        $webclient.DownloadFile($url, $filepath)
-    }
-    return $filepath
-}
-
-
-function ParsePythonVersion ($python_version) {
-    if ($python_version -match $PYTHON_PRERELEASE_REGEX) {
-        return ([int]$matches.major, [int]$matches.minor, [int]$matches.micro,
-                $matches.prerelease)
-    }
-    $version_obj = [version]$python_version
-    return ($version_obj.major, $version_obj.minor, $version_obj.build, "")
-}
-
-
-function DownloadPython ($python_version, $platform_suffix) {
-    $major, $minor, $micro, $prerelease = ParsePythonVersion $python_version
-
-    if (($major -le 2 -and $micro -eq 0) `
-        -or ($major -eq 3 -and $minor -le 2 -and $micro -eq 0) `
-        ) {
-        $dir = "$major.$minor"
-        $python_version = "$major.$minor$prerelease"
-    } else {
-        $dir = "$major.$minor.$micro"
-    }
-
-    if ($prerelease) {
-        if (($major -le 2) `
-            -or ($major -eq 3 -and $minor -eq 1) `
-            -or ($major -eq 3 -and $minor -eq 2) `
-            -or ($major -eq 3 -and $minor -eq 3) `
-            ) {
-            $dir = "$dir/prev"
-        }
-    }
-
-    if (($major -le 2) -or ($major -le 3 -and $minor -le 4)) {
-        $ext = "msi"
-        if ($platform_suffix) {
-            $platform_suffix = ".$platform_suffix"
-        }
-    } else {
-        $ext = "exe"
-        if ($platform_suffix) {
-            $platform_suffix = "-$platform_suffix"
-        }
-    }
-
-    $filename = "python-$python_version$platform_suffix.$ext"
-    $url = "$BASE_URL$dir/$filename"
-    $filepath = Download $filename $url
-    return $filepath
-}
-
-
-function InstallPython ($python_version, $architecture, $python_home) {
-    Write-Host "[NAME_REDACTED]" $python_version "for" $architecture "bit architecture to" $python_home
-    if (Test-Path $python_home) {
-        Write-Host $python_home "already exists, skipping."
-        return $false
-    }
-    if ($architecture -eq "32") {
-        $platform_suffix = ""
-    } else {
-        $platform_suffix = "amd64"
-    }
-    $installer_path = DownloadPython $python_version $platform_suffix
-    $installer_ext = [System.IO.Path]::GetExtension($installer_path)
-    Write-Host "Installing $installer_path to $python_home"
-    $install_log = $python_home + ".log"
-    if ($installer_ext -eq '.msi') {
-        InstallPythonMSI $installer_path $python_home $install_log
-    } else {
-        InstallPythonEXE $installer_path $python_home $install_log
-    }
-    if (Test-Path $python_home) {
-        Write-Host "Python $python_version ($architecture) installation complete"
-    } else {
-        Write-Host "Failed to install Python in $python_home"
-        Get-Content -Path $install_log
-        Exit 1
-    }
-}
-
-
-function InstallPythonEXE ($exepath, $python_home, $install_log) {
-    $install_args = "/quiet InstallAllUsers=1 TargetDir=$python_home"
-    RunCommand $exepath $install_args
-}
-
-
-function InstallPythonMSI ($msipath, $python_home, $install_log) {
-    $install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home"
-    $uninstall_args = "/qn /x $msipath"
-    RunCommand "msiexec.exe" $install_args
-    if (-not(Test-Path $python_home)) {
-        Write-Host "Python seems to be installed else-where, reinstalling."
-        RunCommand "msiexec.exe" $uninstall_args
-        RunCommand "msiexec.exe" $install_args
-    }
-}
-
-function RunCommand ($command, $command_args) {
-    Write-Host $command $command_args
-    Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru
-}
-
-
-function InstallPip ($python_home) {
-    $pip_path = $python_home + "\Scripts\pip.exe"
-    $python_path = $python_home + "\python.exe"
-    if (-not(Test-Path $pip_path)) {
-        Write-Host "Installing pip..."
-        $webclient = [NAME_REDACTED]
-        $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH)
-        Write-Host "Executing:" $python_path $GET_PIP_PATH
-        & $python_path $GET_PIP_PATH
-    } else {
-        Write-Host "pip already installed."
-    }
-}
-
-
-function DownloadMiniconda ($python_version, $platform_suffix) {
-    if ($python_version -eq "3.4") {
-        $filename = "Miniconda3-3.5.5-Windows-" + $platform_suffix + ".exe"
-    } else {
-        $filename = "Miniconda-3.5.5-Windows-" + $platform_suffix + ".exe"
-    }
-    $url = $MINICONDA_URL + $filename
-    $filepath = Download $filename $url
-    return $filepath
-}
-
-
-function InstallMiniconda ($python_version, $architecture, $python_home) {
-    Write-Host "[NAME_REDACTED]" $python_version "for" $architecture "bit architecture to" $python_home
-    if (Test-Path $python_home) {
-        Write-Host $python_home "already exists, skipping."
-        return $false
-    }
-    if ($architecture -eq "32") {
-        $platform_suffix = "x86"
-    } else {
-        $platform_suffix = "x86_64"
-    }
-    $filepath = DownloadMiniconda $python_version $platform_suffix
-    Write-Host "Installing" $filepath "to" $python_home
-    $install_log = $python_home + ".log"
-    $args = "/S /D=$python_home"
-    Write-Host $filepath $args
-    Start-Process -FilePath $filepath -ArgumentList $args -Wait -Passthru
-    if (Test-Path $python_home) {
-        Write-Host "Python $python_version ($architecture) installation complete"
-    } else {
-        Write-Host "Failed to install Python in $python_home"
-        Get-Content -Path $install_log
-        Exit 1
-    }
-}
-
-
-function InstallMinicondaPip ($python_home) {
-    $pip_path = $python_home + "\Scripts\pip.exe"
-    $conda_path = $python_home + "\Scripts\conda.exe"
-    if (-not(Test-Path $pip_path)) {
-        Write-Host "Installing pip..."
-        $args = "install --yes pip"
-        Write-Host $conda_path $args
-        Start-Process -FilePath "$conda_path" -ArgumentList $args -Wait -Passthru
-    } else {
-        Write-Host "pip already installed."
-    }
-}
-
-function main () {
-    InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON
-    InstallPip $env:PYTHON
-}
-
-main

@@ -1,62 +0,0 @@
-# AppVeyor.yml from https://github.com/ogrisel/python-appveyor-demo
-# License: CC0 1.0 Universal: https://creativecommons.org/publicdomain/zero/1.0/
-
-build: off
-
-environment:
-  matrix:
-    - PYTHON: "C:\\Python27-x64"
-      PYTHON_VERSION: "2.7.x"
-      PYTHON_ARCH: "64"
-      TOXENV: "py27"
-
-    - PYTHON: "C:\\Python34-x64"
-      PYTHON_VERSION: "3.4.x"
-      PYTHON_ARCH: "64"
-      TOXENV: "py34"
-
-    - PYTHON: "C:\\Python35-x64"
-      PYTHON_VERSION: "3.5.x"
-      PYTHON_ARCH: "64"
-      TOXENV: "py35"
-
-    - PYTHON: "C:\\Python36-x64"
-      PYTHON_VERSION: "3.6.x"
-      PYTHON_ARCH: "64"
-      TOXENV: "py36"
-
-    - PYTHON: "C:\\Python37-x64"
-      PYTHON_VERSION: "3.7.x"
-      PYTHON_ARCH: "64"
-      TOXENV: "py37"
-
-    - PYTHON: "C:\\Python38-x64"
-      PYTHON_VERSION: "3.8.x"
-      PYTHON_ARCH: "64"
-      TOXENV: "py38"
-
-install:
-  # [NAME_REDACTED] (from the official .msi of https://www.python.org/) and pip when
-  # not already installed.
-  - ps: if (-not(Test-Path($env:PYTHON))) { & _appveyor\install.ps1 }
-
-  # Prepend newly installed Python to the PATH of this build (this cannot be
-  # done from inside the powershell script as it would require to restart
-  # the parent CMD process).
-  - "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
-
-  # Check that we have the expected version and architecture for Python
-  - "python --version"
-  - "python -c \"import struct; print(struct.calcsize('P') * 8)\""
-
-  # Upgrade to the latest version of pip to avoid it displaying warnings
-  # about it being out of date.
-  - "python -m pip install --upgrade pip wheel"
-  - "C:\\MinGW\\bin\\mingw32-make"
-  - "pipenv install -e .[socks] --skip-lock"
-
-test_script:
-  - "C:\\MinGW\\bin\\mingw32-make coverage"
-
-on_success:
-  - "pipenv run codecov -f coverage.xml"

@@ -668,7 +668,7 @@ def test_POSTBIN_GET_POST_FILES(self, httpbin):
         post1 = requests.post(url, data={'some': 'data'})
         assert post1.status_code == 200

-        with open('Pipfile') as f:
+        with open('requirements-dev.txt') as f:
             post2 = requests.post(url, files={'some': f})
         assert post2.status_code == 200

@@ -736,7 +736,7 @@ def test_POSTBIN_GET_POST_FILES_WITH_DATA(self, httpbin):
         post1 = requests.post(url, data={'some': 'data'})
         assert post1.status_code == 200

-        with open('Pipfile') as f:
+        with open('requirements-dev.txt') as f:
             post2 = requests.post(url, data={'some': 'data'}, files={'some': f})
         assert post2.status_code == 200

@@ -773,7 +773,7 @@ def __len__(self):

     def test_conflicting_post_params(self, httpbin):
         url = httpbin('post')
-        with open('Pipfile') as f:
+        with open('requirements-dev.txt') as f:
             with pytest.raises(ValueError):
                 requests.post(url, data='[{"some": "data"}]', files={'some': f})
             with pytest.raises(ValueError):

Woo, excited to see the Pipfile go, that means we don't have to maintain our own dependencies on the urllib3 downstream tests!

Since we're (rightfully) dropping pipenv, should we get rid of the rest of its cruft (Pipenv, Pipenv.lock)?

~I'd been thinking we'd do that in the next minor release. Pipenv has \~1M downloads a month, but I have no idea if anyone even uses the [NAME_REDACTED]. I'm not tied to waiting if you or Seth feel strongly.~
I tested pipenv when we remove the Pipfile and it actually degrades pretty gracefully. Let's just remove it now.


Respect the NETRC environment variable

Resurecting https://github.com/psf/requests/pull/4339 with incorporated review/feedback

Fixes https://github.com/psf/requests/issues/4318

@@ -213,7 +213,8 @@ Note: Custom headers are given less precedence than more specific sources of inf

 * Authorization headers set with `headers=` will be overridden if credentials
   are specified in ``.netrc``, which in turn will be overridden by the  ``auth=``
-  parameter.
+  parameter. Requests will search for the netrc file at `~/.netrc`, `~/_netrc`,
+  or at the path specified by the `NETRC` environment variable.
 * Authorization headers will be removed if you get redirected off-host.
 * Proxy-Authorization headers will be overridden by [NAME_REDACTED] the URL.
 * Content-Length headers will be overridden when we can determine the length of the content.

@@ -169,14 +169,20 @@ def super_len(o):
 def get_netrc_auth(url, raise_errors=False):
     """Returns the Requests tuple auth for a given url from netrc."""

+    netrc_file = os.environ.get('NETRC')
+    if netrc_file is not None:
+        netrc_locations = (netrc_file,)
+    else:
+        netrc_locations = ('~/{}'.format(f) for f in NETRC_FILES)
+
     try:
         from netrc import netrc, NetrcParseError

         netrc_path = None

-        for f in NETRC_FILES:
+        for f in netrc_locations:
             try:
-                loc = os.path.expanduser('~/{}'.format(f))
+                loc = os.path.expanduser(f)
             except KeyError:
                 # os.path.expanduser can fail when $HOME is undefined and
                 # getpwuid fails. See https://bugs.python.org/issue20164 &

Yeah, I think I'm fine with adding support. Left a quick question about the current implementation.

Great, this looks good to me. Thanks [USER_REDACTED]!

Why are you duplicating `.eggs` here? And `.tox` below?

ah jeez, let me fix that...

hrm, I amended the commit and force-pushed on my fork, but this change is still showing up...

ok here goes.

GitHub does that to avoid confusing folks with "I left feedback here! They force-pushed and now it's disappeared!"

This doesn't look quite right. Are all `NETRC` paths relative to the users home directory? I think we only want this formatting for the `NETRC_FILES`.

Actually, it looks like the last PR has [similar feedback](https://github.com/psf/requests/pull/4339#discussion_r144723124). If we can get that or something similar incorporated, I think we're set.

very true... how's this then?

I can also do it exactly as suggested in previous PR... let me know

I'm a little hesitant to mutate this directly with how long it's been around. Let's keep the formatting inside `get_netrc_auth` if possible.

ah makes total sense, wouldn't have thought of that... changing then!

done.


Create codeql-analysis.yml

@@ -0,0 +1,66 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+name: "CodeQL"
+
+on:
+  push:
+    branches: [master]
+  pull_request:
+    # The branches below must be a subset of the branches above
+    branches: [master]
+  schedule:
+    - cron: '0 23 * * 0'
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+
+    strategy:
+      fail-fast: false
+
+
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v2
+      with:
+        # We must fetch at least the immediate parents so that if this is
+        # a pull request then we can checkout the head.
+        fetch-depth: 2
+
+    # If this run was triggered by a pull request event, then checkout
+    # the head of the pull request instead of the merge commit.
+    - run: git checkout HEAD^2
+      if: ${{ github.event_name == 'pull_request' }}
+
+    # Initializes the CodeQL tools for scanning.
+    - name: [NAME_REDACTED]
+      uses: github/codeql-action/init@v1
+      with:
+        languages: "python"
+        # 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.
+        # 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
+
+    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
+    # If this step fails, then you should remove it and run the build manually (see below)
+    - name: Autobuild
+      uses: github/codeql-action/autobuild@v1
+
+    # ℹ Command-line programs to run using the OS shell.
+    # 📚 https://git.io/JvXDl
+
+    # ✏ If the Autobuild fails above, remove it and uncomment the following three lines
+    #    and modify them (or add more) to build your code if your project
+    #    uses a compiled language
+
+    #- run: |
+    #   make bootstrap
+    #   make release
+
+    - name: [NAME_REDACTED]
+      uses: github/codeql-action/analyze@v1

Interesting service! Some of the comments in the yml are a little much, up to you if you want to prune them down a bit.

Also, if this makes security vulnerabilities public have we tried running it locally and fixed issues before making this automated?

LGTM

Can we hard-code `python` here and not use the matrix feature?

Is there value in checking the v3 branch if it's likely not going to be released?

Better question: Is there value in keeping it around if it's not going to be released?

Done

Removed it in the second commit as well

I think there's a lot of valuable work on the proposed/3.0.0 branch that *could* be released at some point. I was keeping it up to date until we had some of the crazier commit history changes in 2018. I think we can get it back in sync but it's going to take a pretty tedious merge. Alternatively, we could hand pick the relevant feature commits, but that's also a big time investment.

That said, I don't think there's any value in checking the current proposal branch here.

Frankly, I think there's a lot of internal refactoring that could make a 3.0 easier to release and maintain long-term. It's something I've thought about for over a year


Don't use pyOpenSSL unless no SNI is detected

This is part 1 on removing `requests[security]` extra, for now we can still support SNI-less installations of Python but instead of unconditional monkey-patching we only patch if we detect either no `ssl` module or no `SNI` support.

Related: https://github.com/psf/requests/issues/5267
cc [USER_REDACTED]

An additional thought I had was if we wanted to warn users about the complete removal of `requests[security]` I don't think there's a way to detect if you were installed via `requests pyopenssl` or `requests[security]` beyond registering a name on PyPI like `requests-security-extra`, trying to import it, and if we do then we know we were installed via `requests[security]` so throw a deprecation warning? Otherwise our "end-goal" could be to have `requests[security]` be empty?

@@ -4,9 +4,14 @@ Release History
 dev
 ---

-**Bugfixes**
+**Improvements**
+
+- pyOpenSSL TLS implementation is now only used if Python
+  either doesn't have an `ssl` module or doesn't support
+  SNI. Previously pyOpenSSL was unconditionally used if available.
+  This applies even if pyOpenSSL is installed via the
+  `requests[security]` extra (#5443)

--   \[Short description of non-trivial change.\]

 2.23.0 (2020-02-19)
 -------------------

@@ -90,14 +90,22 @@ def _check_cryptography(cryptography_version):
                   "version!".format(urllib3.__version__, chardet.__version__),
                   RequestsDependencyWarning)

-# Attempt to enable urllib3's SNI support, if possible
+# Attempt to enable urllib3's fallback for SNI support
+# if the standard library doesn't support SNI or the
+# 'ssl' library isn't available.
 try:
-    from urllib3.contrib import pyopenssl
-    pyopenssl.inject_into_urllib3()
+    try:
+        import ssl
+    except ImportError:
+        ssl = None
+
+    if not getattr(ssl, "HAS_SNI", False):
+        from urllib3.contrib import pyopenssl
+        pyopenssl.inject_into_urllib3()

-    # Check cryptography version
-    from cryptography import __version__ as cryptography_version
-    _check_cryptography(cryptography_version)
+        # Check cryptography version
+        from cryptography import __version__ as cryptography_version
+        _check_cryptography(cryptography_version)
 except ImportError:
     pass

One question but otherwise LGTM

Ok cool, it sounds like we’ve got agreement this is a decent compromise to at least get us moving in the right direction.

Perfect 👌

GitHub is rendering this oddly. Is this a tab or some kind of mixed white space?

That is a strange render but as far as I can tell they're all spaces.


docs: make danger of verify=False clearer

This PR makes the danger of setting `verify=False` clearer to users of requests. In many cases, users may set this to `False` when running into problems locally with self-signed certificates, but not realize the risk they are opening themselves up to.

As an example of how the dangers of not verifying TLS (SSL) certificates is communicated, see Go's `InsecureSkipVerify` docs: https://golang.org/pkg/crypto/tls/

```
    // InsecureSkipVerify controls whether a client verifies the
    // server's certificate chain and host name.
    // If InsecureSkipVerify is true, TLS accepts any certificate
    // presented by [NAME_REDACTED] host name in that certificate.
    // In this mode, TLS is susceptible to man-in-the-middle attacks.
    // This should be used only for testing.
```

Happy to take feedback on the wording / doc-style - this is my first PR to requests but I care a lot about 'safe defaults' in OSS!

@@ -243,6 +243,12 @@ Requests can also ignore verifying the SSL certificate if you set ``verify`` to
     >>> requests.get('https://kennethreitz.org', verify=False)
     <Response [200]>

+Note that when ``verify`` is set to ``False``, requests will accept any TLS
+certificate presented by [NAME_REDACTED], and will ignore hostname mismatches
+and/or expired certificates, which will make your application vulnerable to
+man-in-the-middle (MitM) attacks. Setting verify to ``False`` may be useful
+during local development or testing.
+
 By default, ``verify`` is set to True. Option ``verify`` only applies to host certs.

 [NAME_REDACTED]

@@ -387,6 +387,13 @@ def __init__(self):
         self.stream = False

         #: SSL Verification default.
+        #: Defaults to `True`, requiring requests to verify the TLS certificate at the
+        #: remote end.
+        #: If verify is set to `False`, requests will accept any TLS certificate
+        #: presented by [NAME_REDACTED], and will ignore hostname mismatches and/or
+        #: expired certificates, which will make your application vulnerable to
+        #: man-in-the-middle (MitM) attacks.
+        #: Only set this to `False` for testing.
         self.verify = True

         #: SSL client certificate default, if String, path to ssl client
@@ -495,7 +502,12 @@ def request(self, method, url,
             content. Defaults to ``False``.
         :param verify: (optional) Either a boolean, in which case it controls whether we verify
             the server's TLS certificate, or a string, in which case it must be a path
-            to a CA bundle to use. Defaults to ``True``.
+            to a CA bundle to use. Defaults to ``True``. When set to
+            ``False``, requests will accept any TLS certificate presented by
+            the server, and will ignore hostname mismatches and/or expired
+            certificates, which will make your application vulnerable to
+            man-in-the-middle (MitM) attacks. Setting verify to ``False``
+            may be useful during local development or testing.
         :param cert: (optional) if String, path to ssl client cert file (.pem).
             If Tuple, ('cert', 'key') pair.
         :rtype: requests.Response

LGTM, thank you for this change.

There are (rarely) good cases for setting this to False. I'd remove this line as it's likely to cause a fire-storm for the already overtaxed maintainers of this project

I would agree! The reason that I mentioned this was to get ahead of questions about "why is this configurable in the first place?" - which is a reasonable question for someone to ask about an option like this. I'm not sure I grok the 'firestorm' part, though.

Happy to elide if there's strong feelings about it, but "when would I set this to False" is a fair question for a newcomer to ask.

There are people who jump on things like this as soon as they find it and then they post about it in places to get attention and trigger a pile-on. The project has had issues like this in the past (for example, when it kept it's own certificate bundle copy from Mozilla instead of keeping that in certifi) and so given how sensitive people can be around this particular flag and topic, I'd rather leave this as an uncontroversial item. I think the "Only" in this will be controversial enough that people will get spitting mad and try to rile others up as well

That’s unfortunate, and hopefully we can point folks to prior art in other libraries/PLs. I have seen pile-ons the other way - e.g where the defaults are _unsafe_. But I digress.

To make sure I understand your ask concretely: you want me to remove the “Only set this...” sentence from the comment (only) - ?

Or phrase it as `"You might consider settings to False during local development or testing."` as I think that's the idea you're conveying

Done.


Send only one Host header in chunked request

Send only one Host header when specifying a custom Host header in a chunked request.

Before this fix, the default Host header was sent along with the custom Host header.

Closes #5274

@@ -191,3 +191,4 @@ Patches and Suggestions
 - "[NAME_REDACTED]" <[EMAIL_REDACTED]> ([CONTRIBUTOR_REDACTED])
 - [NAME_REDACTED] ([CONTRIBUTOR_REDACTED])
 - Sylvain Marié ([CONTRIBUTOR_REDACTED])
+- [NAME_REDACTED] ([CONTRIBUTOR_REDACTED])

@@ -458,9 +458,11 @@ def send(self, request, stream=False, timeout=None, verify=True, cert=None, prox
                 low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)

                 try:
+                    skip_host = 'Host' in request.headers
                     low_conn.putrequest(request.method,
                                         url,
-                                        skip_accept_encoding=True)
+                                        skip_accept_encoding=True,
+                                        skip_host=skip_host)

                     for header, value in request.headers.items():
                         low_conn.putheader(header, value)

@@ -9,6 +9,18 @@
 from .utils import override_environ

+def echo_response_handler(sock):
+    """Simple handler that will take request and echo it back to requester."""
+    request_content = consume_socket_content(sock, timeout=0.5)
+
+    text_200 = (
+        b'HTTP/1.1 200 OK\r\n'
+        b'Content-Length: %d\r\n\r\n'
+        b'%s'
+    ) % (len(request_content), request_content)
+    sock.send(text_200)
+
+
 def test_chunked_upload():
     """can safely send generators"""
     close_server = threading.Event()
@@ -46,6 +58,42 @@ def incomplete_chunked_response_handler(sock):
         close_server.set()  # release server block

+def test_chunked_upload_uses_only_specified_host_header():
+    """Ensure we use only the specified Host header for chunked requests."""
+    close_server = threading.Event()
+    server = Server(echo_response_handler, wait_to_close_event=close_server)
+
+    data = iter([b'a', b'b', b'c'])
+    custom_host = 'sample-host'
+
+    with server as (host, port):
+        url = 'http://{}:{}/'.format(host, port)
+        r = requests.post(url, data=data, headers={'Host': custom_host}, stream=True)
+        close_server.set()  # release server block
+
+    expected_header = b'Host: %s\r\n' % custom_host.encode('utf-8')
+    assert expected_header in r.content
+    assert r.content.count(b'Host: ') == 1
+
+
+def test_chunked_upload_doesnt_skip_host_header():
+    """Ensure we don't omit all Host headers with chunked requests."""
+    close_server = threading.Event()
+    server = Server(echo_response_handler, wait_to_close_event=close_server)
+
+    data = iter([b'a', b'b', b'c'])
+
+    with server as (host, port):
+        expected_host = '{}:{}'.format(host, port)
+        url = 'http://{}:{}/'.format(host, port)
+        r = requests.post(url, data=data, stream=True)
+        close_server.set()  # release server block
+
+    expected_header = b'Host: %s\r\n' % expected_host.encode('utf-8')
+    assert expected_header in r.content
+    assert r.content.count(b'Host: ') == 1
+
+
 def test_conflicting_content_lengths():
     """Ensure we correctly throw an InvalidHeader error if multiple
     conflicting Content-Length headers are returned.

Thanks for digging in a finding a simple solution here, [USER_REDACTED]! This seems like a straight forward win that alleviates the risks of other patches we've had for #5274. Apologies this has been sitting so long.

I've added some minor test refactoring to make these easier to expand later and rebased your changes onto our `main` branch. I think this should be set!

That's good, this appears to be the same way that the same situation is handled in the urllib3 code itself: https://github.com/urllib3/urllib3/blob/74d6be1ab66cef44c0f479c24b0fc1756a8fe4e9/src/urllib3/connection.py#L243-L246


Remove explicit unicode literal in README.md and doc

As the end of python 2 has come, I open this PR in order to get rid of the explicit unicode literal in the README file and the documentation.
I know that this could be against the PEP 414 https://www.python.org/dev/peps/pep-0414/ but it should be harmless for the project.

@@ -35,9 +35,9 @@ Release v\ |version|. (:ref:`Installation <install>`)
     >>> r.encoding
     'utf-8'
     >>> r.text
-    u'{"type":"User"...'
+    '{"type":"User"...'
     >>> r.json()
-    {u'private_gists': 419, u'total_private_repos': 77, ...}
+    {'private_gists': 419, 'total_private_repos': 77, ...}

 See `similar code, sans Requests <https://gist.github.com/973705>`_.

@@ -698,12 +698,12 @@ So, GitHub returns JSON. That's great, we can use the :meth:`r.json
     >>> commit_data = r.json()

     >>> print(commit_data.keys())
-    [u'committer', u'author', u'url', u'tree', u'sha', u'parents', u'message']
+    ['committer', 'author', 'url', 'tree', 'sha', 'parents', 'message']

-    >>> print(commit_data[u'committer'])
-    {u'date': u'2012-05-10T11:10:50-07:00', u'email': u'[EMAIL_REDACTED]', u'name': u'[NAME_REDACTED]'}
+    >>> print(commit_data['committer'])
+    {'date': '2012-05-10T11:10:50-07:00', 'email': '[EMAIL_REDACTED]', 'name': '[NAME_REDACTED]'}

-    >>> print(commit_data[u'message'])
+    >>> print(commit_data['message'])
     makin' history

 So far, so simple. Well, let's investigate the GitHub API a little bit. Now,
@@ -746,34 +746,34 @@ this issue already exists, we will use it as an example. Let's start by getting

     >>> issue = json.loads(r.text)

-    >>> print(issue[u'title'])
+    >>> print(issue['title'])
     Feature any http verb in docs

-    >>> print(issue[u'comments'])
+    >>> print(issue['comments'])
     3

 Cool, we have three comments. Let's take a look at the last of them.

 ::

-    >>> r = requests.get(r.url + u'/comments')
+    >>> r = requests.get(r.url + '/comments')
     >>> r.status_code
     200

     >>> comments = r.json()

     >>> print(comments[0].keys())
-    [u'body', u'url', u'created_at', u'updated_at', u'user', u'id']
+    ['body', 'url', 'created_at', 'updated_at', 'user', 'id']

-    >>> print(comments[2][u'body'])
+    >>> print(comments[2]['body'])
     Probably in the "advanced" section

 Well, that seems like a silly place. Let's post a comment telling the poster
 that he's silly. Who is the poster, anyway?

 ::

-    >>> print(comments[2][u'user'][u'login'])
+    >>> print(comments[2]['user']['login'])
     kennethreitz

 OK, so let's tell this Kenneth guy that we think this example should go in the
@@ -803,7 +803,7 @@ the very common [NAME_REDACTED].
     201

     >>> content = r.json()
-    >>> print(content[u'body'])
+    >>> print(content['body'])
     Sounds great! I'll get right on it.

 Brilliant. Oh, wait, no! I meant to add that it would take me a while, because

@@ -93,7 +93,7 @@ again::

     >>> r = requests.get('https://api.github.com/events')
     >>> r.text
-    u'[{"repository":{"open_issues":0,"url":"https://github.com/...
+    '[{"repository":{"open_issues":0,"url":"https://github.com/...

 Requests will automatically decode content from the server. Most unicode
 charsets are seamlessly decoded.
@@ -148,7 +148,7 @@ There's also a builtin JSON decoder, in case you're dealing with JSON data::

     >>> r = requests.get('https://api.github.com/events')
     >>> r.json()
-    [{u'repository': {u'open_issues': 0, u'url': 'https://github.com/...
+    [{'repository': {'open_issues': 0, 'url': 'https://github.com/...

 In case the JSON decoding fails, ``r.json()`` raises an exception. For example, if
 the response gets a 204 ([NAME_REDACTED]), or if the response contains invalid JSON,

This looks good to me, would you mind squashing and rebasing onto master?

Thanks [USER_REDACTED]!

Missed one here

Thanks! I'll fix it as soon as possible.

Fixed!


Part II: The Principle of Polarity

Love & Hated appear orthogonal, yet are extracted from the same substance & essences. The opposite of Love is not Hatred — but Apathy.

> Everything is dual; Everything has poles; Everything has its pair of opposites; Like and unlike are the same; Opposites are identical in nature, but different in degree; Extremes meet; All truths, are but half-truths; All paradoxes may be reconciled.

[As above, so below](https://www.kennethreitz.org/as-above-so-below).

☤

@@ -1,16 +1,14 @@
-Requests is written and maintained by [NAME_REDACTED]
-various contributors:
+Requests was lovingly created by [NAME_REDACTED] the Crystals
 ```````````````````````

-- [NAME_REDACTED] <[EMAIL_REDACTED]> [CONTRIBUTOR_REDACTED], Keeper of the Master Crystal.
 - [NAME_REDACTED] <[EMAIL_REDACTED]> [CONTRIBUTOR_REDACTED]
 - [NAME_REDACTED] <[EMAIL_REDACTED]> [CONTRIBUTOR_REDACTED]

 Previous Keepers of Crystals
 ````````````````````````````
-
+- [NAME_REDACTED] <[EMAIL_REDACTED]> [CONTRIBUTOR_REDACTED], reluctant Keeper of the Master Crystal.
 - [NAME_REDACTED] <[EMAIL_REDACTED]> [CONTRIBUTOR_REDACTED]

@@ -1,9 +1,10 @@

-
+

-
+
+

      Python 3.7.4 (default, Sep  7 2019, 18:27:02)
@@ -12,15 +13,11 @@
      >>> r.json()["description"]
      'A simple, yet elegant HTTP library. Handcrafted, with ♥, for the Python community.'

-
-
-
+

-This software has been designed for you, with much joy,
-by [NAME_REDACTED] & is secured by The [NAME_REDACTED].
+This software has been designed for you, with much joy,
+by [NAME_REDACTED] & is protected by The [NAME_REDACTED].

-

-

@@ -71,14 +68,14 @@ Requests is ready for the demands of building robust and reliable HTTP–speak a

          + [NAME_REDACTED] and URLs       + Keep-Alive & [NAME_REDACTED]
-         + Sessions with [NAME_REDACTED]     + Browser-style SSL Verification
-         + Basic & [NAME_REDACTED]        + Familiar `dict`–like Cookies
-         + [NAME_REDACTED] of Content   + [NAME_REDACTED]
+         + Sessions with [NAME_REDACTED]     + Browser-style SSL Verification
+         + Basic & [NAME_REDACTED]        + Familiar `dict`–like Cookies
+         + [NAME_REDACTED] of Content   + [NAME_REDACTED]
          + [NAME_REDACTED]         + [NAME_REDACTED]<super>*</super>
-         + [NAME_REDACTED]              + SOCKS [NAME_REDACTED]
+         + [NAME_REDACTED]              + SOCKS [NAME_REDACTED]
          + [NAME_REDACTED]                  + [NAME_REDACTED]
-         + Automatic honoring of `.netrc`       + Chunked HTTP Requests
-
+         + Automatic honoring of `.netrc`       + Chunked HTTP Requests
+
                             &, of course, rock–solid stability!

@@ -127,4 +124,3 @@ Requests officially supports Python 2.7 & 3.4–3.8.

-

@@ -0,0 +1 @@
+Copyright 2019 [NAME_REDACTED] reserved.

**Note**: this is for the images contained within the `ext` folder, which includes a [tattoo upon my arm](https://images.squarespace-cdn.com/content/v1/533ad9bde4b098d084a846b1/1469928300857-S9WKXBO0CCRX0RL4PJYX/ke17ZwdGBToddI8pDm48kKLHmMs23ZxPgeOu8Yrs55d7gQa3H78H3Y0txjaiv_0fDoOvxcdMmMKkDsyUqMSsMWxHk725yiiHCCLfrh8O1z4YTzHvnKhyp6Da-NYroOW3ZGjoBKy3azqku80C789l0iyqMbMesKd95J-X4EagrgXXATmnXitsEasKvdtVty7KcIpiUOmkkpuOP9PWkdpi9w/L1000129-Edit.jpg?format=1500w) — I had the tattoo before I made it the Requests logo :)


[fix] fix failing doctests in requests/*

## Why this PR
Running the command:
`
$ py.test --doctest-module requests
`
gives failing doctests. This PR aims to fix these failing doctests.

[NAME_REDACTED]: https://github.com/[USER_REDACTED]nethreitz/requests/issues/4986

## How to test
`
$ py.test --doctest-module requests
`
All the 11 doctests should pass.

@@ -189,5 +189,7 @@ Patches and Suggestions
 - [NAME_REDACTED] ([CONTRIBUTOR_REDACTED])
 - [NAME_REDACTED] ([CONTRIBUTOR_REDACTED])
 - [NAME_REDACTED] ([CONTRIBUTOR_REDACTED])
+- "[NAME_REDACTED]" <[EMAIL_REDACTED]> ([CONTRIBUTOR_REDACTED])
+- [NAME_REDACTED] ([CONTRIBUTOR_REDACTED])
 - [NAME_REDACTED] ([CONTRIBUTOR_REDACTED])
 - [NAME_REDACTED] <[EMAIL_REDACTED]> ([CONTRIBUTOR_REDACTED])

@@ -1,2 +1,3 @@
 [pytest]
-addopts = -p no:warnings
\ No newline at end of file
+addopts = -p no:warnings --doctest-modules
+doctest_optionflags= NORMALIZE_WHITESPACE ELLIPSIS
\ No newline at end of file

@@ -9,14 +9,14 @@
 Requests HTTP Library
 ~~~~~~~~~~~~~~~~~~~~~

-Requests is an HTTP library, written in Python, for human beings. Basic GET
-usage:
+Requests is an HTTP library, written in Python, for human beings.
+Basic GET usage:

    >>> import requests
    >>> r = requests.get('https://www.python.org')
    >>> r.status_code
    200
-   >>> 'Python is a programming language' in r.content
+   >>> b'Python is a programming language' in r.content
    True

 ... or POST:
@@ -27,8 +27,8 @@
    {
      ...
      "form": {
-       "key2": "value2",
-       "key1": "value1"
+       "key1": "value1",
+       "key2": "value2"
      },
      ...
    }

@@ -50,6 +50,7 @@ def request(method, url, **kwargs):

       >>> import requests
       >>> req = requests.request('GET', 'https://httpbin.org/get')
+      >>> req
       <Response [200]>
     """

@@ -280,6 +280,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
       >>> import requests
       >>> req = requests.Request('GET', 'https://httpbin.org/get')
       >>> r = req.prepare()
+      >>> r
       <PreparedRequest [GET]>

       >>> s = requests.Session()

@@ -354,7 +354,7 @@ class Session(SessionRedirectMixin):
     Or as a context manager::

       >>> with requests.Session() as s:
-      >>>     s.get('https://httpbin.org/get')
+      ...     s.get('https://httpbin.org/get')
       <Response [200]>
     """

@@ -4,7 +4,7 @@
 The ``codes`` object defines a mapping from common names for HTTP statuses
 to their numerical codes, accessible either as attributes or as dictionary
 items.
-
+>>> import requests
 >>> requests.codes['temporary_redirect']
 307
 >>> requests.codes.teapot

@@ -267,6 +267,8 @@ def from_key_val_list(value):
         >>> from_key_val_list([('key', 'val')])
         OrderedDict([('key', 'val')])
         >>> from_key_val_list('string')
+        Traceback (most recent call last):
+        ...
         ValueError: cannot encode objects that are not 2-tuples
         >>> from_key_val_list({'key': 'val'})
         OrderedDict([('key', 'val')])
@@ -293,7 +295,9 @@ def to_key_val_list(value):
         >>> to_key_val_list({'key': 'val'})
         [('key', 'val')]
         >>> to_key_val_list('string')
-        ValueError: cannot encode objects that are not 2-tuples.
+        Traceback (most recent call last):
+        ...
+        ValueError: cannot encode objects that are not 2-tuples

     :rtype: list
     """

Hi, I would suggest to do so:
```
   >>> print(r.text)
   ... # doctest: +ELLIPSIS
   ... # doctest: +NORMALIZE_WHITESPACE
   {
     ...
     "form": {
       "key2": "value2",
       "key1": "value1"
     },
     ...
   }
```
So we change the test minimally.

Thanks. I did not know that I could use this syntax. I'm going to change it according to your suggestion.

I added NORMALIZE_WHITESPACE and ELLIPSIS to the pytest.ini file and removed from the doctest in order to use them in pytest configuration.


Normalize percent-encoded bytes before comparison

urllib3 replaced it's URL parser with `rfc3986` for more compliant parsing (urllib3/urllib3#1487). We've also got an effort underway to run downstream tests before releases (urllib3/urllib3#1508) and during that process we've discovered that requests requires percent-encoded bytes to keep their casing.

Adding this to the unit tests would allow either version of pre- and post-rfc3986 urllib3 to pass requests unit tests. I don't know if the `Request.url` attribute needs to maintain it's casing in this situation or not, if it does this is not the solution. Just putting this together as a potential solution.

@@ -38,5 +38,5 @@ jobs:
       dist: xenial
       sudo: true
     - stage: coverage
-      python: 3.6
+      python: '3.6'
       script: codecov

@@ -1,7 +1,7 @@
 .PHONY: docs
 init:
 	pip install pipenv --upgrade
-	pipenv install --dev --skip-lock
+	pipenv install --dev
 test:
 	# This runs all of the tests, on both Python 2 and Python 3.
 	detox

@@ -4,7 +4,7 @@ verify_ssl = true
 name = "pypi"

 [dev-packages]
-pytest = ">=2.8.0"
+pytest = ">=2.8.0,<4.1"
 codecov = "*"
 pytest-httpbin = ">=0.0.7"
 pytest-mock = "*"

@@ -9,6 +9,7 @@
 import collections
 import contextlib
 import warnings
+import re

 import io
 import requests
@@ -2418,9 +2419,17 @@ class TestPreparingURLs(object):
         )
     )
     def test_preparing_url(self, url, expected):
+
+        def normalize_percent_encode(x):
+            # Helper function that normalizes equivalent
+            # percent-encoded bytes before comparisons
+            for c in re.findall(r'%[a-fA-F0-9]{2}', x):
+                x = x.replace(c, c.upper())
+            return x
+
         r = requests.Request('GET', url=url)
         p = r.prepare()
-        assert p.url == expected
+        assert normalize_percent_encode(p.url) == expected

     [USER_REDACTED].mark.parametrize(
         'url',

pchars don't have case-sensitivity as the RFC describes them so I see no issue with this fix for our tests.

This is odd


v2.20.0

2.20.0 (2018-10-??)
-------------------
**Bugfixes**

-   Content-Type header parsing is now case-insensitive (e.g.
    charset=utf8 v Charset=utf8).
-   Fixed exception leak where certain redirect urls would raise
    uncaught urllib3 exceptions.
-   Requests removes Authorization headers from requests redirected
    from https to http on the same hostname. (CVE-2018-18074)
-   `should_bypass_proxies` now handles URIs without hostnames, e.g. files.

**Dependencies**
- We now support urllib3 v1.24.

**Deprecations**
- Requests has officially stopped support for Python 2.6.

@@ -125,7 +125,7 @@ Patches and Suggestions
 - [NAME_REDACTED] <[EMAIL_REDACTED]> ([CONTRIBUTOR_REDACTED])
 - [NAME_REDACTED] <[EMAIL_REDACTED]> ([CONTRIBUTOR_REDACTED])
 - [NAME_REDACTED] <[EMAIL_REDACTED]> ([CONTRIBUTOR_REDACTED])
-- [NAME_REDACTED] <[EMAIL_REDACTED]> ([CONTRIBUTOR_REDACTED])
+- [NAME_REDACTED] <[EMAIL_REDACTED]> ([CONTRIBUTOR_REDACTED])
 - Łukasz Langa <[EMAIL_REDACTED]>
 - [NAME_REDACTED] <[EMAIL_REDACTED]>
 - [NAME_REDACTED] ([CONTRIBUTOR_REDACTED])

@@ -6,12 +6,29 @@ dev

 **Bugfixes**

+-   \[Short description of non-trivial change.\]
+
+2.20.0 (2018-10-18)
+-------------------
+
+**Bugfixes**
+
 -   Content-Type header parsing is now case-insensitive (e.g.
     charset=utf8 v Charset=utf8).
 -   Fixed exception leak where certain redirect urls would raise
     uncaught urllib3 exceptions.
+-   Requests removes Authorization header from requests redirected
+    from https to http on the same hostname. (CVE-2018-18074)
+-   `should_bypass_proxies` now handles URIs without hostnames (e.g.
+    files).

--   \[Short description of non-trivial change.\]
+**Dependencies**
+
+- Requests now supports urllib3 v1.24.
+
+**Deprecations**
+
+- Requests has officially stopped support for Python 2.6.

 2.19.1 (2018-06-14)
 -------------------

@@ -57,10 +57,10 @@ def check_compatibility(urllib3_version, chardet_version):
     # Check urllib3 for compatibility.
     major, minor, patch = urllib3_version  # noqa: F811
     major, minor, patch = int(major), int(minor), int(patch)
-    # urllib3 >= 1.21.1, <= 1.23
+    # urllib3 >= 1.21.1, <= 1.24
     assert major == 1
     assert minor >= 21
-    assert minor <= 23
+    assert minor <= 24

     # Check chardet for compatibility.
     major, minor, patch = chardet_version.split('.')[:3]
@@ -79,14 +79,14 @@ def _check_cryptography(cryptography_version):
         return

     if cryptography_version < [1, 3, 4]:
-        warning = 'Old version of cryptography ({0}) may cause slowdown.'.format(cryptography_version)
+        warning = 'Old version of cryptography ({}) may cause slowdown.'.format(cryptography_version)
         warnings.warn(warning, RequestsDependencyWarning)

 # Check imported dependencies for compatibility.
 try:
     check_compatibility(urllib3.__version__, chardet.__version__)
 except (AssertionError, ValueError):
-    warnings.warn("urllib3 ({0}) or chardet ({1}) doesn't match a supported "
+    warnings.warn("urllib3 ({}) or chardet ({}) doesn't match a supported "
                   "version!".format(urllib3.__version__, chardet.__version__),
                   RequestsDependencyWarning)

@@ -123,12 +123,7 @@ def _check_cryptography(cryptography_version):

 # Set default logging handler to avoid "No handler found" warnings.
 import logging
-try:  # Python 2.7+
-    from logging import NullHandler
-except ImportError:
-    class NullHandler(logging.Handler):
-        def emit(self, record):
-            pass
+from logging import NullHandler

 logging.getLogger(__name__).addHandler(NullHandler())

@@ -5,8 +5,8 @@
 __title__ = 'requests'
 __description__ = 'Python HTTP for Humans.'
 __url__ = 'http://python-requests.org'
-__version__ = '2.19.1'
-__build__ = 0x021901
+__version__ = '2.20.0'
+__build__ = 0x022000
 __author__ = '[NAME_REDACTED]'
 __author_email__ = '[EMAIL_REDACTED]'
 __license__ = 'Apache 2.0'

@@ -129,8 +129,7 @@ def __init__(self, pool_connections=DEFAULT_POOLSIZE,
         self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block)

     def __getstate__(self):
-        return dict((attr, getattr(self, attr, None)) for attr in
-                    self.__attrs__)
+        return {attr: getattr(self, attr, None) for attr in self.__attrs__}

     def __setstate__(self, state):
         # Can't handle by adding 'proxy_manager' to self.__attrs__ because
@@ -226,7 +225,7 @@ def cert_verify(self, conn, url, verify, cert):

             if not cert_loc or not os.path.exists(cert_loc):
                 raise IOError("Could not find a suitable TLS CA certificate bundle, "
-                              "invalid path: {0}".format(cert_loc))
+                              "invalid path: {}".format(cert_loc))

             conn.cert_reqs = 'CERT_REQUIRED'

@@ -248,10 +247,10 @@ def cert_verify(self, conn, url, verify, cert):
                 conn.key_file = None
             if conn.cert_file and not os.path.exists(conn.cert_file):
                 raise IOError("Could not find the TLS certificate file, "
-                              "invalid path: {0}".format(conn.cert_file))
+                              "invalid path: {}".format(conn.cert_file))
             if conn.key_file and not os.path.exists(conn.key_file):
                 raise IOError("Could not find the TLS key file, "
-                              "invalid path: {0}".format(conn.key_file))
+                              "invalid path: {}".format(conn.key_file))

     def build_response(self, req, resp):
         """Builds a :class:`Response <requests.Response>` object from a urllib3
@@ -426,7 +425,7 @@ def send(self, request, stream=False, timeout=None, verify=True, cert=None, prox
                 timeout = TimeoutSauce(connect=connect, read=read)
             except ValueError as e:
                 # this may raise a string formatting error.
-                err = ("Invalid timeout {0}. Pass a (connect, read) "
+                err = ("Invalid timeout {}. Pass a (connect, read) "
                        "timeout tuple, or a single float to set "
                        "both timeouts to the same value".format(timeout))
                 raise ValueError(err)
@@ -476,11 +475,10 @@ def send(self, request, stream=False, timeout=None, verify=True, cert=None, prox

                     # Receive the response from the server
                     try:
-                        # For Python 2.7+ versions, use buffering of HTTP
-                        # responses
+                        # For Python 2.7, use buffering of HTTP responses
                         r = low_conn.getresponse(buffering=True)
                     except TypeError:
-                        # For compatibility with Python 2.6 versions and back
+                        # For compatibility with Python 3.3+
                         r = low_conn.getresponse()

                     resp = HTTPResponse.from_httplib(

@@ -38,7 +38,7 @@ def _basic_auth_str(username, password):
     if not isinstance(username, basestring):
         warnings.warn(
             "Non-string usernames will no longer be supported in Requests "
-            "3.0.0. Please convert the object you've passed in ({0!r}) to "
+            "3.0.0. Please convert the object you've passed in ({!r}) to "
             "a string or bytes object in the near future to avoid "
             "problems.".format(username),
             category=DeprecationWarning,
@@ -48,7 +48,7 @@ def _basic_auth_str(username, password):
     if not isinstance(password, basestring):
         warnings.warn(
             "Non-string passwords will no longer be supported in Requests "
-            "3.0.0. Please convert the object you've passed in ({0!r}) to "
+            "3.0.0. Please convert the object you've passed in ({!r}) to "
             "a string or bytes object in the near future to avoid "
             "problems.".format(password),
             category=DeprecationWarning,

@@ -43,9 +43,8 @@
     import cookielib
     from Cookie import Morsel
     from StringIO import StringIO
-    from collections import Callable, Mapping, MutableMapping
+    from collections import Callable, Mapping, MutableMapping, OrderedDict

-    from urllib3.packages.ordered_dict import OrderedDict

     builtin_str = str
     bytes = str

@@ -444,20 +444,21 @@ def create_cookie(name, value, **kwargs):
     By default, the pair of `name` and `value` will be set for the domain ''
     and sent on every request (this is sometimes called a "supercookie").
     """
-    result = dict(
-        version=0,
-        name=name,
-        value=value,
-        port=None,
-        domain='',
-        path='/',
-        secure=False,
-        expires=None,
-        discard=True,
-        comment=None,
-        comment_url=None,
-        rest={'HttpOnly': None},
-        rfc2109=False,)
+    result = {
+        'version': 0,
+        'name': name,
+        'value': value,
+        'port': None,
+        'domain': '',
+        'path': '/',
+        'secure': False,
+        'expires': None,
+        'discard': True,
+        'comment': None,
+        'comment_url': None,
+        'rest': {'HttpOnly': None},
+        'rfc2109': False,
+    }

     badargs = set(kwargs) - set(result)
     if badargs:

@@ -89,8 +89,7 @@ def info():
         'version': getattr(idna, '__version__', ''),
     }

-    # OPENSSL_VERSION_NUMBER doesn't exist in the Python 2.6 ssl module.
-    system_ssl = getattr(ssl, 'OPENSSL_VERSION_NUMBER', None)
+    system_ssl = ssl.OPENSSL_VERSION_NUMBER
     system_ssl_info = {
         'version': '%x' % system_ssl if system_ssl is not None else ''
     }

@@ -15,14 +15,14 @@

 def default_hooks():
-    return dict((event, []) for event in HOOKS)
+    return {event: [] for event in HOOKS}

 # TODO: response is the only one

 def dispatch_hook(key, hooks, hook_data, **kwargs):
     """Dispatches a hook dictionary on a given piece of data."""
-    hooks = hooks or dict()
+    hooks = hooks or {}
     hooks = hooks.get(key)
     if hooks:
         if hasattr(hooks, '__call__'):

@@ -652,10 +652,7 @@ def __getstate__(self):
         if not self._content_consumed:
             self.content

-        return dict(
-            (attr, getattr(self, attr, None))
-            for attr in self.__attrs__
-        )
+        return {attr: getattr(self, attr, None) for attr in self.__attrs__}

     def __setstate__(self, state):
         for name, value in state.items():

@@ -738,7 +738,7 @@ def mount(self, prefix, adapter):
             self.adapters[key] = self.adapters.pop(key)

     def __getstate__(self):
-        state = dict((attr, getattr(self, attr, None)) for attr in self.__attrs__)
+        state = {attr: getattr(self, attr, None) for attr in self.__attrs__}
         return state

     def __setstate__(self, state):

@@ -173,7 +173,7 @@ def get_netrc_auth(url, raise_errors=False):

         for f in NETRC_FILES:
             try:
-                loc = os.path.expanduser('~/{0}'.format(f))
+                loc = os.path.expanduser('~/{}'.format(f))
             except KeyError:
                 # os.path.expanduser can fail when $HOME is undefined and
                 # getpwuid fails. See https://bugs.python.org/issue20164 &
@@ -729,21 +729,16 @@ def should_bypass_proxies(url, no_proxy):
         else:
             host_with_port = parsed.hostname
             if parsed.port:
-                host_with_port += ':{0}'.format(parsed.port)
+                host_with_port += ':{}'.format(parsed.port)

             for host in no_proxy:
                 if parsed.hostname.endswith(host) or host_with_port.endswith(host):
                     # The URL does match something in no_proxy, so we don't want
                     # to apply the proxies on this URL.
                     return True

-    # If the system proxy settings indicate that this URL should be bypassed,
-    # don't proxy.
-    # The proxy_bypass function is incredibly buggy on OS X in early versions
-    # of Python 2.6, so allow this call to fail. Only catch the specific
-    # exceptions we've seen, though: this call failing in other ways can reveal
-    # legitimate problems.
     with set_environ('no_proxy', no_proxy_arg):
+        # parsed.hostname can be `None` in cases such as a file URI.
         try:
             bypass = proxy_bypass(parsed.hostname)
         except (TypeError, socket.gaierror):

@@ -39,18 +39,12 @@ def run_tests(self):
     os.system('twine upload dist/*')
     sys.exit()

-# pyOpenSSL version 18.0.0 dropped support for Python 2.6
-if sys.version_info < (2, 7):
-    PYOPENSSL_VERSION = 'pyOpenSSL >= 0.14, < 18.0.0'
-else:
-    PYOPENSSL_VERSION = 'pyOpenSSL >= 0.14'
-
 packages = ['requests']

 requires = [
     'chardet>=3.0.2,<3.1.0',
     'idna>=2.5,<2.8',
-    'urllib3>=1.21.1,<1.24',
+    'urllib3>=1.21.1,<1.25',
     'certifi>=2017.4.17'

 ]
@@ -85,7 +79,7 @@ def run_tests(self):
     package_data={'': ['LICENSE', 'NOTICE'], 'requests': ['*.pem']},
     package_dir={'requests': 'requests'},
     include_package_data=True,
-    python_requires=">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
+    python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
     install_requires=requires,
     license=about['__license__'],
     zip_safe=False,
@@ -108,8 +102,8 @@ def run_tests(self):
     cmdclass={'test': PyTest},
     tests_require=test_requirements,
     extras_require={
-        'security': [PYOPENSSL_VERSION, 'cryptography>=1.3.4', 'idna>=2.0.0'],
+        'security': ['pyOpenSSL >= 0.14', 'cryptography>=1.3.4', 'idna>=2.0.0'],
         'socks': ['PySocks>=1.5.6, !=1.5.7'],
-        'socks:sys_platform == "win32" and (python_version == "2.7" or python_version == "2.6")': ['win_inet_pton'],
+        'socks:sys_platform == "win32" and python_version == "2.7"': ['win_inet_pton'],
     },
 )

@@ -7,15 +7,6 @@
 from requests.help import info

-[USER_REDACTED].mark.skipif(sys.version_info[:2] != (2,6), reason="Only run on Python 2.6")
-def test_system_ssl_py26():
-    """OPENSSL_VERSION_NUMBER isn't provided in Python 2.6, verify we don't
-    blow up in this case.
-    """
-    assert info()['system_ssl'] == {'version': ''}
-
-
-[USER_REDACTED].mark.skipif(sys.version_info < (2,7), reason="Only run on Python 2.7+")
 def test_system_ssl():
     """Verify we're actually setting system_ssl when it should be available."""
     assert info()['system_ssl']['version'] != ''

@@ -16,7 +16,7 @@ def test_chunked_upload():
     data = iter([b'a', b'b', b'c'])

     with server as (host, port):
-        url = 'http://{0}:{1}/'.format(host, port)
+        url = 'http://{}:{}/'.format(host, port)
         r = requests.post(url, data=data, stream=True)
         close_server.set()  # release server block

@@ -77,7 +77,7 @@ def digest_response_handler(sock):
     server = Server(digest_response_handler, wait_to_close_event=close_server)

     with server as (host, port):
-        url = 'http://{0}:{1}/'.format(host, port)
+        url = 'http://{}:{}/'.format(host, port)
         r = requests.get(url, auth=auth)
         # Verify server succeeded in authenticating.
         assert r.status_code == 200
@@ -127,7 +127,7 @@ def digest_failed_response_handler(sock):
     server = Server(digest_failed_response_handler, wait_to_close_event=close_server)

     with server as (host, port):
-        url = 'http://{0}:{1}/'.format(host, port)
+        url = 'http://{}:{}/'.format(host, port)
         r = requests.get(url, auth=auth)
         # Verify server didn't authenticate us.
         assert r.status_code == 401
@@ -164,7 +164,7 @@ def digest_response_handler(sock):
     server = Server(digest_response_handler, wait_to_close_event=close_server)

     with server as (host, port):
-        url = 'http://{0}:{1}/'.format(host, port)
+        url = 'http://{}:{}/'.format(host, port)
         r = requests.get(url, auth=auth)
         # Verify server didn't receive auth from us.
         assert r.status_code == 200
@@ -181,17 +181,17 @@ def digest_response_handler(sock):
 _proxy_combos = []
 for prefix, schemes in _schemes_by_var_prefix:
     for scheme in schemes:
-        _proxy_combos.append(("{0}_proxy".format(prefix), scheme))
+        _proxy_combos.append(("{}_proxy".format(prefix), scheme))

 _proxy_combos += [(var.upper(), scheme) for var, scheme in _proxy_combos]

 [USER_REDACTED].mark.parametrize("var,scheme", _proxy_combos)
 def test_use_proxy_from_environment(httpbin, var, scheme):
-    url = "{0}://httpbin.org".format(scheme)
+    url = "{}://httpbin.org".format(scheme)
     fake_proxy = Server()  # do nothing with the requests; just close the socket
     with fake_proxy as (host, port):
-        proxy_url = "socks5://{0}:{1}".format(host, port)
+        proxy_url = "socks5://{}:{}".format(host, port)
         kwargs = {var: proxy_url}
         with override_environ(**kwargs):
             # fake proxy's lack of response will cause a ConnectionError
@@ -212,7 +212,7 @@ def test_redirect_rfc1808_to_non_ascii_location():

     def redirect_resp_handler(sock):
         consume_socket_content(sock, timeout=0.5)
-        location = u'//{0}:{1}/{2}'.format(host, port, path)
+        location = u'//{}:{}/{}'.format(host, port, path)
         sock.send(
             b'HTTP/1.1 301 Moved Permanently\r\n'
             b'Content-Length: 0\r\n'
@@ -226,13 +226,13 @@ def redirect_resp_handler(sock):
     server = Server(redirect_resp_handler, wait_to_close_event=close_server)

     with server as (host, port):
-        url = u'http://{0}:{1}'.format(host, port)
+        url = u'http://{}:{}'.format(host, port)
         r = requests.get(url=url, allow_redirects=True)
         assert r.status_code == 200
         assert len(r.history) == 1
         assert r.history[0].status_code == 301
         assert redirect_request[0].startswith(b'GET /' + expected_path + b' HTTP/1.1')
-        assert r.url == u'{0}/{1}'.format(url, expected_path.decode('ascii'))
+        assert r.url == u'{}/{}'.format(url, expected_path.decode('ascii'))

         close_server.set()

@@ -250,7 +250,7 @@ def response_handler(sock):
     server = Server(response_handler, wait_to_close_event=close_server)

     with server as (host, port):
-        url = 'http://{0}:{1}/path/to/thing/#view=edit&token=hunter2'.format(host, port)
+        url = 'http://{}:{}/path/to/thing/#view=edit&token=hunter2'.format(host, port)
         r = requests.get(url)
         raw_request = r.content

@@ -293,7 +293,7 @@ def response_handler(sock):
     server = Server(response_handler, wait_to_close_event=close_server)

     with server as (host, port):
-        url = 'http://{0}:{1}/path/to/thing/#view=edit&token=hunter2'.format(host, port)
+        url = 'http://{}:{}/path/to/thing/#view=edit&token=hunter2'.format(host, port)
         r = requests.get(url)
         raw_request = r.content

@@ -302,8 +302,8 @@ def response_handler(sock):
         assert r.history[0].request.url == url

         # Verify we haven't overwritten the location with our previous fragment.
-        assert r.history[1].request.url == 'http://{0}:{1}/get#relevant-section'.format(host, port)
+        assert r.history[1].request.url == 'http://{}:{}/get#relevant-section'.format(host, port)
         # Verify previous fragment is used and not the original.
-        assert r.url == 'http://{0}:{1}/final-url/#relevant-section'.format(host, port)
+        assert r.url == 'http://{}:{}/final-url/#relevant-section'.format(host, port)

         close_server.set()

@@ -158,7 +158,7 @@ def test_mixed_case_scheme_acceptable(self, httpbin, scheme):
         url = scheme + parts.netloc + parts.path
         r = requests.Request('GET', url)
         r = s.send(r.prepare())
-        assert r.status_code == 200, 'failed for scheme {0}'.format(scheme)
+        assert r.status_code == 200, 'failed for scheme {}'.format(scheme)

     def test_HTTP_200_OK_GET_ALTERNATIVE(self, httpbin):
         r = requests.Request('GET', httpbin('get'))
@@ -816,17 +816,17 @@ def test_invalid_ca_certificate_path(self, httpbin_secure):
         INVALID_PATH = '/garbage'
         with pytest.raises(IOError) as e:
             requests.get(httpbin_secure(), verify=INVALID_PATH)
-        assert str(e.value) == 'Could not find a suitable TLS CA certificate bundle, invalid path: {0}'.format(INVALID_PATH)
+        assert str(e.value) == 'Could not find a suitable TLS CA certificate bundle, invalid path: {}'.format(INVALID_PATH)

     def test_invalid_ssl_certificate_files(self, httpbin_secure):
         INVALID_PATH = '/garbage'
         with pytest.raises(IOError) as e:
             requests.get(httpbin_secure(), cert=INVALID_PATH)
-        assert str(e.value) == 'Could not find the TLS certificate file, invalid path: {0}'.format(INVALID_PATH)
+        assert str(e.value) == 'Could not find the TLS certificate file, invalid path: {}'.format(INVALID_PATH)

         with pytest.raises(IOError) as e:
             requests.get(httpbin_secure(), cert=('.', INVALID_PATH))
-        assert str(e.value) == 'Could not find the TLS key file, invalid path: {0}'.format(INVALID_PATH)
+        assert str(e.value) == 'Could not find the TLS key file, invalid path: {}'.format(INVALID_PATH)

     def test_http_with_certificate(self, httpbin):
         r = requests.get(httpbin(), cert='.')
@@ -864,7 +864,7 @@ def test_certificate_failure(self, httpbin_secure):

     def test_urlencoded_get_query_multivalued_param(self, httpbin):

-        r = requests.get(httpbin('get'), params=dict(test=['foo', 'baz']))
+        r = requests.get(httpbin('get'), params={'test': ['foo', 'baz']})
         assert r.status_code == 200
         assert r.url == httpbin('get?test=foo&test=baz')

@@ -1458,7 +1458,7 @@ def test_params_are_merged_case_sensitive(self, httpbin):
         assert r.json()['args'] == {'foo': 'bar', 'FOO': 'bar'}

     def test_long_authinfo_in_url(self):
-        url = 'http://{0}:{1}@{2}:9000/path?query#frag'.format(
+        url = 'http://{}:{}@{}:9000/path?query#frag'.format(
             'E8A3BE87-9E3F-4620-8858-95478E385B5B',
             'EA770032-DA4D-4D84-8CE9-29C6D910BF1E',
             'exactly-------------sixty-----------three------------characters',

@@ -50,7 +50,7 @@ def test_text_response(self):
         )

         with server as (host, port):
-            r = requests.get('http://{0}:{1}'.format(host, port))
+            r = requests.get('http://{}:{}'.format(host, port))

             assert r.status_code == 200
             assert r.text == u'roflol'
@@ -59,7 +59,7 @@ def test_text_response(self):
     def test_basic_response(self):
         """the basic response server returns an empty http response"""
         with Server.basic_response_server() as (host, port):
-            r = requests.get('http://{0}:{1}'.format(host, port))
+            r = requests.get('http://{}:{}'.format(host, port))
             assert r.status_code == 200
             assert r.text == u''
             assert r.headers['Content-Length'] == '0'
@@ -83,7 +83,7 @@ def test_multiple_requests(self):
         server = Server.basic_response_server(requests_to_handle=requests_to_handle)

         with server as (host, port):
-            server_url = 'http://{0}:{1}'.format(host, port)
+            server_url = 'http://{}:{}'.format(host, port)
             for _ in range(requests_to_handle):
                 r = requests.get(server_url)
                 assert r.status_code == 200

@@ -665,7 +665,7 @@ def test_add_dict_to_cookiejar(cookiejar):
     cookiedict = {'test': 'cookies',
                   'good': 'cookies'}
     cj = add_dict_to_cookiejar(cookiejar, cookiedict)
-    cookies = dict((cookie.name, cookie.value) for cookie in cj)
+    cookies = {cookie.name: cookie.value for cookie in cj}
     assert cookiedict == cookies

We'll need to update the date here before merging.


Prefer https:// for URLs throughout project

@@ -646,7 +646,7 @@ documentation](http://docs.python-requests.org/en/latest/community/release-proce
 **Bugfixes**

 -   Revert changes to our vendored certificate bundle. For more context
-    see (\#2455, \#2456, and <http://bugs.python.org/issue23476>)
+    see (\#2455, \#2456, and <https://bugs.python.org/issue23476>)

 2.5.2 (2015-02-23)
 ------------------

@@ -4,7 +4,7 @@ Copyright 2018 [NAME_REDACTED] not use this file except in compliance with the License.
    You may obtain a copy of the License at

-       http://www.apache.org/licenses/LICENSE-2.0
+       https://www.apache.org/licenses/LICENSE-2.0

    Unless required by [NAME_REDACTED] to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,

@@ -226,4 +226,4 @@ function main () {
     InstallPip $env:PYTHON
 }

-main
\ No newline at end of file
+main

@@ -1,5 +1,5 @@
 # AppVeyor.yml from https://github.com/ogrisel/python-appveyor-demo
-# License: CC0 1.0 Universal: http://creativecommons.org/publicdomain/zero/1.0/
+# License: CC0 1.0 Universal: https://creativecommons.org/publicdomain/zero/1.0/

 build: off

@@ -31,7 +31,7 @@ environment:
       TOXENV: "py37"

 install:
-  # [NAME_REDACTED] (from the official .msi of http://python.org) and pip when
+  # [NAME_REDACTED] (from the official .msi of https://www.python.org/) and pip when
   # not already installed.
   - ps: if (-not(Test-Path($env:PYTHON))) { & _appveyor\install.ps1 }

@@ -25,17 +25,17 @@
[NAME_REDACTED]

   allowtransparency="true" frameborder="0" scrolling="0" width="200" height="20">

Follow [USER_REDACTED]

-
[NAME_REDACTED].

+
[NAME_REDACTED].

[NAME_REDACTED]

-
More [NAME_REDACTED] projects:

+
More [NAME_REDACTED] projects:

-
Requests-HTML

+
Requests-HTML

howtopython.org

pipenv

-
pep8.org

-
httpbin.org

-
The Python Guide

+
pep8.org

+
httpbin.org

+
The Python Guide

Maya: Datetimes for Humans

Records: SQL for Humans

Legit: Git for Humans

@@ -20,7 +20,7 @@

[NAME_REDACTED]

Receive updates on new releases and upcoming projects.

-
[NAME_REDACTED].

+
[NAME_REDACTED].

@@ -34,14 +34,14 @@
[NAME_REDACTED]

[NAME_REDACTED]

-
More [NAME_REDACTED] projects:

+
More [NAME_REDACTED] projects:

-
Requests-HTML

+
Requests-HTML

howtopython.org

pipenv

-
pep8.org

-
httpbin.org

-
The Python Guide

+
pep8.org

+
httpbin.org

+
The Python Guide

Maya: Datetimes for Humans

Records: SQL for Humans

Legit: Git for Humans

@@ -139,7 +139,7 @@ API Changes
       s = requests.Session()    # formerly, session took parameters
       s.auth = auth
       s.headers.update(headers)
-      r = s.get('http://httpbin.org/headers')
+      r = s.get('https://httpbin.org/headers')

 * All request hooks have been removed except 'response'.

@@ -185,7 +185,7 @@ API Changes
       requests_log.setLevel(logging.DEBUG)
       requests_log.propagate = True

-      requests.get('http://httpbin.org/headers')
+      requests.get('https://httpbin.org/headers')

@@ -197,8 +197,8 @@ license from the ISC_ license to the `Apache 2.0`_ license. The Apache 2.0
 license ensures that contributions to Requests are also covered by [NAME_REDACTED]
 2.0 license.

-.. _ISC: http://opensource.org/licenses/ISC
-.. _Apache 2.0: http://opensource.org/licenses/Apache-2.0
+.. _ISC: https://opensource.org/licenses/ISC
+.. _Apache 2.0: https://opensource.org/licenses/Apache-2.0

 Migrating to 2.x
@@ -213,7 +213,7 @@ For more details on the changes in this release including new APIs, links
 to the relevant GitHub issues and some of the bug fixes, read Cory's blog_
 on the subject.

-.. _blog: http://lukasa.co.uk/2013/09/Requests_20/
+.. _blog: https://lukasa.co.uk/2013/09/Requests_20/

 API Changes

@@ -15,10 +15,10 @@ To give it a try, simply::

 Articles & Talks
 ================
-- `Python for the Web <http://gun.io/blog/python-for-the-web/>`_ teaches how to use Python to interact with the web, using Requests.
-- `[NAME_REDACTED] of Requests <http://pydanny.blogspot.com/2011/05/python-http-requests-for-humans.html>`_
-- `My 'Python for Humans' talk <http://python-for-humans.heroku.com>`_ ( `audio <http://codeconf.s3.amazonaws.com/2011/pycodeconf/talks/PyCodeConf2011%20-%20Kenneth%20Reitz.m4a>`_ )
+- `Python for the Web <https://www.gun.io/blog/python-for-the-web>`_ teaches how to use Python to interact with the web, using Requests.
+- `[NAME_REDACTED] of Requests <https://pydanny.blogspot.com/2011/05/python-http-requests-for-humans.html>`_
+- `My 'Python for Humans' talk <http://python-for-humans.heroku.com>`_ ( `audio <https://codeconf.s3.amazonaws.com/2011/pycodeconf/talks/PyCodeConf2011%20-%20Kenneth%20Reitz.m4a>`_ )
 - `[NAME_REDACTED] '[NAME_REDACTED] APIs' talk <https://issackelly.github.com/Consuming-Web-APIs-with-Python-Talk/slides/slides.html>`_
-- `Blog post about Requests via Yum <http://arunsag.wordpress.com/2011/08/17/new-package-python-requests-http-for-humans/>`_
-- `Russian blog post introducing Requests <http://habrahabr.ru/blogs/python/126262/>`_
+- `Blog post about Requests via Yum <https://arunsag.wordpress.com/2011/08/17/new-package-python-requests-http-for-humans/>`_
+- `Russian blog post introducing Requests <https://habr.com/post/126262/>`_
 - `Sending JSON in Requests <http://www.coglib.com/~icordasc/blog/2014/11/sending-json-in-requests.html>`_

@@ -157,7 +157,7 @@ model methods (e.g. ``__repr__``) are typically the exception to this rule.

 Thanks for helping to make the world a better place!

-.. _PEP 8: http://pep8.org
+.. _PEP 8: https://pep8.org/
 .. _line continuations: https://www.python.org/dev/peps/pep-0008/#indentation

 Documentation Contributions

@@ -9,7 +9,7 @@ Requests is an open but opinionated library, created by [NAME_REDACTED]
 [NAME_REDACTED]
 ~~~~~~~~~~~~~~~~

-`[NAME_REDACTED] <http://kennethreitz.org>`_ is the BDFL. He has final say in any decision related to the Requests project. Kenneth is responsible for the direction and form of the library, as well as its presentation. In addition to making decisions based on technical merit, he is responsible for making decisions based on the development philosophy of Requests.
+`[NAME_REDACTED] <https://www.kennethreitz.org/>`_ is the BDFL. He has final say in any decision related to the Requests project. Kenneth is responsible for the direction and form of the library, as well as its presentation. In addition to making decisions based on technical merit, he is responsible for making decisions based on the development philosophy of Requests.

 `[NAME_REDACTED] <http://www.coglib.com/~icordasc/>`_ and `[NAME_REDACTED] <https://lukasa.co.uk/about/>`_ are the core contributors. They are responsible for triaging bug reports, reviewing pull requests and ensuring that Kenneth is kept up to speed with developments around the library. The day-to-day managing of the project is done by [NAME_REDACTED] are responsible for making judgements about whether or not a feature request is likely to be accepted by Kenneth. Their word is, in some ways, more final than Kenneth's.

@@ -26,7 +26,7 @@ [NAME_REDACTED] many years, the open source community has been plagued with version number dystonia. Numbers vary so greatly from project to project, they are practically meaningless.

-Requests uses `[NAME_REDACTED] <http://semver.org>`_. This specification seeks to put an end to this madness with a small set of practical guidelines for you and your colleagues to use in your next project.
+Requests uses `[NAME_REDACTED] <https://semver.org/>`_. This specification seeks to put an end to this madness with a small set of practical guidelines for you and your colleagues to use in your next project.

 [NAME_REDACTED]?
 ~~~~~~~~~~~~~~~~~

@@ -25,8 +25,8 @@ Let's persist some cookies across requests::

     s = requests.Session()

-    s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
-    r = s.get('http://httpbin.org/cookies')
+    s.get('https://httpbin.org/cookies/set/sessioncookie/123456789')
+    r = s.get('https://httpbin.org/cookies')

     print(r.text)
     # '{"cookies": {"sessioncookie": "123456789"}}'
@@ -40,7 +40,7 @@ is done by [NAME_REDACTED] properties on a Session object::
     s.headers.update({'x-test': 'true'})

     # both 'x-test' and 'x-test2' are sent
-    s.get('http://httpbin.org/headers', headers={'x-test2': 'true'})
+    s.get('https://httpbin.org/headers', headers={'x-test2': 'true'})

 Any dictionaries that you pass to a request method will be merged with the
@@ -53,11 +53,11 @@ with the first request, but not the second::

     s = requests.Session()

-    r = s.get('http://httpbin.org/cookies', cookies={'from-my': 'browser'})
+    r = s.get('https://httpbin.org/cookies', cookies={'from-my': 'browser'})
     print(r.text)
     # '{"cookies": {"from-my": "browser"}}'

-    r = s.get('http://httpbin.org/cookies')
+    r = s.get('https://httpbin.org/cookies')
     print(r.text)
     # '{"cookies": {}}'

@@ -69,7 +69,7 @@ If you want to manually add cookies to your session, use the
 Sessions can also be used as context managers::

     with requests.Session() as s:
-        s.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
+        s.get('https://httpbin.org/cookies/set/sessioncookie/123456789')

 This will make sure the session is closed as soon as the ``with`` block is
 exited, even if unhandled exceptions occurred.
@@ -97,7 +97,7 @@ The ``Response`` object contains all of the information returned by [NAME_REDACTED] a
 also contains the ``Request`` object you created originally. Here is a simple
 request to get some very important information from Wikipedia's servers::

-    >>> r = requests.get('http://en.wikipedia.org/wiki/Monty_Python')
+    >>> r = requests.get('https://en.wikipedia.org/wiki/Monty_Python')

 If we want to access the headers the server sent back to us, we do this::

@@ -323,7 +323,7 @@ inefficiency with connections. If you find yourself partially reading request
 bodies (or not reading them at all) while using ``stream=True``, you should
 make the request within a ``with`` statement to ensure it's always closed::

-    with requests.get('http://httpbin.org/get', stream=True) as r:
+    with requests.get('https://httpbin.org/get', stream=True) as r:
         # Do things with the response here.

 .. _keep-alive:
@@ -393,7 +393,7 @@ upload image files to an HTML form with a multiple file field 'images'::

 To do that, just set files to a list of tuples of ``(form_field_name, file_info)``::

-    >>> url = 'http://httpbin.org/post'
+    >>> url = 'https://httpbin.org/post'
     >>> multiple_files = [
             ('images', ('foo.png', open('foo.png', 'rb'), 'image/png')),
             ('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))]
@@ -455,13 +455,13 @@ anything, nothing else is affected.

 Let's print some request method arguments at runtime::

-    >>> requests.get('http://httpbin.org', hooks={'response': print_url})
-    http://httpbin.org
+    >>> requests.get('https://httpbin.org/', hooks={'response': print_url})
+    https://httpbin.org/
     <Response [200]>

 You can add multiple hooks to a single request.  Let's call two hooks at once::

-    >>> r = requests.get('http://httpbin.org', hooks={'response': [print_url, record_hook]})
+    >>> r = requests.get('https://httpbin.org/', hooks={'response': [print_url, record_hook]})
     >>> r.hook_called
     True

@@ -470,8 +470,8 @@ be called on every request made to the session.  For example::

    >>> s = requests.Session()
    >>> s.hooks['response'].append(print_url)
-   >>> s.get('http://httpbin.org')
-    http://httpbin.org
+   >>> s.get('https://httpbin.org/')
+    https://httpbin.org/
     <Response [200]>

 A ``Session`` can have multiple hooks, which will be called in the order
@@ -529,7 +529,7 @@ set ``stream`` to ``True`` and iterate over the response with
     import json
     import requests

-    r = requests.get('http://httpbin.org/stream/20', stream=True)
+    r = requests.get('https://httpbin.org/stream/20', stream=True)

     for line in r.iter_lines():

@@ -543,7 +543,7 @@ When using `decode_unicode=True` with
 :meth:`Response.iter_content() <requests.Response.iter_content>`, you'll want
 to provide a fallback encoding in the event the server doesn't provide one::

-    r = requests.get('http://httpbin.org/stream/20', stream=True)
+    r = requests.get('https://httpbin.org/stream/20', stream=True)

     if r.encoding is None:
         r.encoding = 'utf-8'
@@ -657,7 +657,7 @@ encoding in the HTTP header, and if none is present, will use `chardet
 The only time Requests will not do this is if no explicit charset
 is present in the HTTP headers **and** the ``Content-Type``
 header contains ``text``. In this situation, `RFC 2616
-<http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1>`_ specifies
+<https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1>`_ specifies
 that the default charset must be ``ISO-8859-1``. Requests follows the
 specification in this case. If you require a different encoding, you can
 manually set the :attr:`Response.encoding <requests.Response.encoding>`
@@ -921,7 +921,7 @@ it should apply to.
 ::

     >>> s = requests.Session()
-    >>> s.mount('http://www.github.com', MyAdapter())
+    >>> s.mount('https://github.com/', MyAdapter())

 The mount call registers a specific instance of a [NAME_REDACTED] to a
 prefix. Once mounted, any HTTP request made using that session whose URL starts
@@ -959,7 +959,7 @@ library to use SSLv3::
                 num_pools=connections, maxsize=maxsize,
                 block=block, ssl_version=ssl.PROTOCOL_SSLv3)

-.. _`described here`: http://www.kennethreitz.org/essays/the-future-of-python-http
+.. _`described here`: https://www.kennethreitz.org/essays/the-future-of-python-http
 .. _`urllib3`: https://github.com/shazow/urllib3

 .. _blocking-or-nonblocking:
@@ -1003,7 +1003,7 @@ The **connect** timeout is the number of seconds Requests will wait for your
 client to establish a connection to a remote machine (corresponding to the
 `connect()`_) call on the socket. It's a good practice to set connect timeouts
 to slightly larger than a multiple of 3, which is the default `TCP packet
-retransmission window <http://www.hjp.at/doc/rfc/rfc2988.txt>`_.
+retransmission window <https://www.hjp.at/doc/rfc/rfc2988.txt>`_.

 Once your client has connected to the server and sent the HTTP request, the
 **read** timeout is the number of seconds the client will wait for the server
@@ -1028,4 +1028,4 @@ coffee.

     r = requests.get('https://github.com', timeout=None)

-.. _`connect()`: http://linux.die.net/man/2/connect
+.. _`connect()`: https://linux.die.net/man/2/connect

@@ -53,7 +53,7 @@ Another very popular form of HTTP Authentication is [NAME_REDACTED],
 and Requests supports this out of the box as well::

     >>> from requests.auth import HTTPDigestAuth
-    >>> url = 'http://httpbin.org/digest-auth/auth/user/pass'
+    >>> url = 'https://httpbin.org/digest-auth/auth/user/pass'
     >>> requests.get(url, auth=HTTPDigestAuth('user', 'pass'))
     <Response [200]>

@@ -122,7 +122,7 @@ To do so, subclass :class:`AuthBase <requests.auth.AuthBase>` and implement the
     ...         # Implement my authentication
     ...         return r
     ...
-    >>> url = 'http://httpbin.org/get'
+    >>> url = 'https://httpbin.org/get'
     >>> requests.get(url, auth=MyAuth())
     <Response [200]>

@@ -134,7 +134,7 @@ authentication will additionally add hooks to provide further functionality.
 Further examples can be found under the `Requests organization`_ and in the
 ``auth.py`` file.

-.. _OAuth: http://oauth.net/
+.. _OAuth: https://oauth.net/
 .. _requests_oauthlib: https://github.com/requests/requests-oauthlib
 .. _requests-oauthlib OAuth2 documentation: https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html
 .. _Web [NAME_REDACTED]: https://requests-oauthlib.readthedocs.io/en/latest/oauth2_workflow.html#web-application-flow

@@ -18,7 +18,7 @@ To install Requests, simply run this simple command in your terminal of choice::
     $ pipenv install requests

 If you don't have `pipenv <http://pipenv.org/>`_ installed (tisk tisk!), head over to the Pipenv website for installation instructions. Or, if you prefer to just use pip and don't have it installed,
-`this Python installation guide <http://docs.python-guide.org/en/latest/starting/installation/>`_
+`this Python installation guide <https://docs.python-guide.org/starting/installation/>`_
 can guide you through the process.

 Get the [NAME_REDACTED]

@@ -37,8 +37,8 @@ closed-source software.

 Requests is released under terms of `Apache2 License`_.

-.. _`GPL Licensed`: http://www.opensource.org/licenses/gpl-license.php
-.. _`Apache2 License`: http://opensource.org/licenses/Apache-2.0
+.. _`GPL Licensed`: https://opensource.org/licenses/gpl-license.php
+.. _`Apache2 License`: https://opensource.org/licenses/Apache-2.0

 [NAME_REDACTED]

@@ -39,15 +39,15 @@ get all the information we need from this object.
 Requests' simple API means that all forms of HTTP request are as obvious. For
 example, this is how you make an HTTP POST request::

-    >>> r = requests.post('http://httpbin.org/post', data = {'key':'value'})
+    >>> r = requests.post('https://httpbin.org/post', data = {'key':'value'})

 Nice, right? What about the other HTTP request types: PUT, DELETE, HEAD and
 OPTIONS? These are all just as simple::

-    >>> r = requests.put('http://httpbin.org/put', data = {'key':'value'})
-    >>> r = requests.delete('http://httpbin.org/delete')
-    >>> r = requests.head('http://httpbin.org/get')
-    >>> r = requests.options('http://httpbin.org/get')
+    >>> r = requests.put('https://httpbin.org/put', data = {'key':'value'})
+    >>> r = requests.delete('https://httpbin.org/delete')
+    >>> r = requests.head('https://httpbin.org/get')
+    >>> r = requests.options('https://httpbin.org/get')

 That's all well and good, but it's also only the start of what Requests can
 do.
@@ -65,12 +65,12 @@ using the ``params`` keyword argument. As an example, if you wanted to pass
 following code::

     >>> payload = {'key1': 'value1', 'key2': 'value2'}
-    >>> r = requests.get('http://httpbin.org/get', params=payload)
+    >>> r = requests.get('https://httpbin.org/get', params=payload)

 You can see that the URL has been correctly encoded by [NAME_REDACTED]::

     >>> print(r.url)
-    http://httpbin.org/get?key2=value2&key1=value1
+    https://httpbin.org/get?key2=value2&key1=value1

 Note that any dictionary key whose value is ``None`` will not be added to the
 URL's query string.
@@ -79,9 +79,9 @@ You can also pass a list of items as a value::

     >>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']}

-    >>> r = requests.get('http://httpbin.org/get', params=payload)
+    >>> r = requests.get('https://httpbin.org/get', params=payload)
     >>> print(r.url)
-    http://httpbin.org/get?key1=value1&key2=value2&key2=value3
+    https://httpbin.org/get?key1=value1&key2=value2&key2=value3

 [NAME_REDACTED]
 ----------------
@@ -233,7 +233,7 @@ dictionary of data will automatically be form-encoded when the request is made::

     >>> payload = {'key1': 'value1', 'key2': 'value2'}

-    >>> r = requests.post("http://httpbin.org/post", data=payload)
+    >>> r = requests.post("https://httpbin.org/post", data=payload)
     >>> print(r.text)
     {
       ...
@@ -250,9 +250,9 @@ as values. This is particularly useful when the form has multiple elements that
 use the same key::

     >>> payload_tuples = [('key1', 'value1'), ('key1', 'value2')]
-    >>> r1 = requests.post('http://httpbin.org/post', data=payload_tuples)
+    >>> r1 = requests.post('https://httpbin.org/post', data=payload_tuples)
     >>> payload_dict = {'key1': ['value1', 'value2']}
-    >>> r2 = requests.post('http://httpbin.org/post', data=payload_dict)
+    >>> r2 = requests.post('https://httpbin.org/post', data=payload_dict)
     >>> print(r1.text)
     {
       ...
@@ -296,7 +296,7 @@ POST a [NAME_REDACTED] makes it simple to upload Multipart-encoded files::

-    >>> url = 'http://httpbin.org/post'
+    >>> url = 'https://httpbin.org/post'
     >>> files = {'file': open('report.xls', 'rb')}

     >>> r = requests.post(url, files=files)
@@ -311,7 +311,7 @@ Requests makes it simple to upload Multipart-encoded files::

 You can set the filename, content_type and headers explicitly::

-    >>> url = 'http://httpbin.org/post'
+    >>> url = 'https://httpbin.org/post'
     >>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})}

     >>> r = requests.post(url, files=files)
@@ -326,7 +326,7 @@ You can set the filename, content_type and headers explicitly::

 If you want, you can send strings to be received as files::

-    >>> url = 'http://httpbin.org/post'
+    >>> url = 'https://httpbin.org/post'
     >>> files = {'file': ('report.csv', 'some,data,to,send\nanother,row,to,send\n')}

     >>> r = requests.post(url, files=files)
@@ -360,7 +360,7 @@ [NAME_REDACTED]

 We can check the response status code::

-    >>> r = requests.get('http://httpbin.org/get')
+    >>> r = requests.get('https://httpbin.org/get')
     >>> r.status_code
     200

@@ -374,7 +374,7 @@ If we made a bad request (a 4XX client error or 5XX server error response), we
 can raise it with
 :meth:`Response.raise_for_status() <requests.Response.raise_for_status>`::

-    >>> bad_r = requests.get('http://httpbin.org/status/404')
+    >>> bad_r = requests.get('https://httpbin.org/status/404')
     >>> bad_r.status_code
     404

@@ -410,7 +410,7 @@ We can view the server's response headers using a Python dictionary::
     }

 The dictionary is special, though: it's made just for HTTP headers. According to
-`RFC 7230 <http://tools.ietf.org/html/rfc7230#section-3.2>`_, HTTP Header names
+`RFC 7230 <https://tools.ietf.org/html/rfc7230#section-3.2>`_, HTTP Header names
 are case-insensitive.

 So, we can access the headers using any capitalization we want::
@@ -424,7 +424,7 @@ So, we can access the headers using any capitalization we want::
 It is also special in that the server could have sent the same header multiple
 times with different values, but requests combines them so they can be
 represented in the dictionary within a single mapping, as per
-`RFC 7230 <http://tools.ietf.org/html/rfc7230#section-3.2>`_:
+`RFC 7230 <https://tools.ietf.org/html/rfc7230#section-3.2>`_:

     A recipient MAY combine multiple header fields with the same field name
     into one "field-name: field-value" pair, without changing the semantics
@@ -445,7 +445,7 @@ If a response contains some Cookies, you can quickly access them::
 To send your own cookies to the server, you can use the ``cookies``
 parameter::

-    >>> url = 'http://httpbin.org/cookies'
+    >>> url = 'https://httpbin.org/cookies'
     >>> cookies = dict(cookies_are='working')

     >>> r = requests.get(url, cookies=cookies)
@@ -460,7 +460,7 @@ also be passed in to requests::
     >>> jar = requests.cookies.RequestsCookieJar()
     >>> jar.set('tasty_cookie', 'yum', domain='httpbin.org', path='/cookies')
     >>> jar.set('gross_cookie', 'blech', domain='httpbin.org', path='/elsewhere')
-    >>> url = 'http://httpbin.org/cookies'
+    >>> url = 'https://httpbin.org/cookies'
     >>> r = requests.get(url, cookies=jar)
     >>> r.text
     '{"cookies": {"tasty_cookie": "yum"}}'
@@ -481,7 +481,7 @@ response.

 For example, GitHub redirects all HTTP requests to HTTPS::

-    >>> r = requests.get('http://github.com')
+    >>> r = requests.get('https://github.com/')

     >>> r.url
     'https://github.com/'
@@ -496,7 +496,7 @@ For example, GitHub redirects all HTTP requests to HTTPS::
 If you're using GET, OPTIONS, POST, PUT, PATCH or DELETE, you can disable
 redirection handling with the ``allow_redirects`` parameter::

-    >>> r = requests.get('http://github.com', allow_redirects=False)
+    >>> r = requests.get('https://github.com/', allow_redirects=False)

     >>> r.status_code
     301
@@ -506,7 +506,7 @@ redirection handling with the ``allow_redirects`` parameter::

 If you're using HEAD, you can enable redirection as well::

-    >>> r = requests.head('http://github.com', allow_redirects=True)
+    >>> r = requests.head('https://github.com/', allow_redirects=True)

     >>> r.url
     'https://github.com/'
@@ -523,7 +523,7 @@ seconds with the ``timeout`` parameter. Nearly all production code should use
 this parameter in nearly all requests. Failure to do so can cause your program
 to hang indefinitely::

-    >>> requests.get('http://github.com', timeout=0.001)
+    >>> requests.get('https://github.com/', timeout=0.001)
     Traceback (most recent call last):
       File "<stdin>", line 1, in <module>
     requests.exceptions.Timeout: HTTPConnectionPool(host='github.com', port=80): Request timed out. (timeout=0.001)

@@ -22,7 +22,7 @@
 ... or POST:

    >>> payload = dict(key1='value1', key2='value2')
-   >>> r = requests.post('http://httpbin.org/post', data=payload)
+   >>> r = requests.post('https://httpbin.org/post', data=payload)
    >>> print(r.text)
    {
      ...

@@ -49,7 +49,7 @@ def request(method, url, **kwargs):
     Usage::

       >>> import requests
-      >>> req = requests.request('GET', 'http://httpbin.org/get')
+      >>> req = requests.request('GET', 'https://httpbin.org/get')
       <Response [200]>
     """

@@ -218,7 +218,7 @@ class Request(RequestHooksMixin):
     Usage::

       >>> import requests
-      >>> req = requests.Request('GET', 'http://httpbin.org/get')
+      >>> req = requests.Request('GET', 'https://httpbin.org/get')
       >>> req.prepare()
       <PreparedRequest [GET]>
     """
@@ -278,7 +278,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
     Usage::

       >>> import requests
-      >>> req = requests.Request('GET', 'http://httpbin.org/get')
+      >>> req = requests.Request('GET', 'https://httpbin.org/get')
       >>> r = req.prepare()
       <PreparedRequest [GET]>

@@ -311,7 +311,7 @@ def rebuild_method(self, prepared_request, response):
         """
         method = prepared_request.method

-        # http://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':
             method = 'GET'

@@ -337,13 +337,13 @@ class Session(SessionRedirectMixin):

       >>> import requests
       >>> s = requests.Session()
-      >>> s.get('http://httpbin.org/get')
+      >>> s.get('https://httpbin.org/get')
       <Response [200]>

     Or as a context manager::

       >>> with requests.Session() as s:
-      >>>     s.get('http://httpbin.org/get')
+      >>>     s.get('https://httpbin.org/get')
       <Response [200]>
     """

@@ -176,7 +176,7 @@ def get_netrc_auth(url, raise_errors=False):
                 loc = os.path.expanduser('~/{0}'.format(f))
             except KeyError:
                 # os.path.expanduser can fail when $HOME is undefined and
-                # getpwuid fails. See http://bugs.python.org/issue20164 &
+                # getpwuid fails. See https://bugs.python.org/issue20164 &
                 # https://github.com/requests/requests/issues/1846
                 return

Looks like a couple examples may be broken. I left comments on the two I had a concern about. Once those are addressed and we get this rebased onto master, I think this is set for merge.

Cool, I think this looks good. Thanks [USER_REDACTED]! ✨

Why was this removed?

Same question here.