Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add extra headers option to enhance HTTP requests #8030

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions news/4475.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Add extra headers option to enhance HTTP requests

Users can supply --extra-headers='{...}' option to pip commands that enhances the
PipSession object with custom headers.

This enables use of private PyPI servers that use token-based authentication.

Example:

```
pip install \
--extra-headers='{"Authorization": "..."}' \
--index-url https://secure.pypi.example.com/simple \
--trusted-host secure.pypi.example.com \
fizz==1.2.3
```
12 changes: 12 additions & 0 deletions src/pip/_internal/cli/cmdoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,17 @@ def exists_action():
) # type: Callable[..., Option]


def extra_headers():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to add it to the install and download (and to probably more ones that I don't remember) CLI. You should add it as a req file option

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the hints, I wasn't quite sure where to drop this code.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be okay to put it in the general_group?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgive my ignorance, but what do you mean "You should add it as a req file option"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the req file- see SUPPORTED_VERSIONS in req_file.py

About the general- it does not makes sense as there are pip commands which do not make any requests at all, you can add to install cmd, download cmd, etc...

# type: () -> Option
return Option(
'--extra-headers',
dest='extra_headers',
metavar='JSON',
default=None,
help='Extra HTTP request headers JSON.',
)


def extra_index_url():
# type: () -> Option
return Option(
Expand Down Expand Up @@ -969,5 +980,6 @@ def check_list_path_option(options):
extra_index_url,
no_index,
find_links,
extra_headers,
]
} # type: Dict[str, Any]
19 changes: 18 additions & 1 deletion src/pip/_internal/cli/req_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
PackageFinder machinery and all its vendored dependencies, etc.
"""

import json
import logging
import os
from functools import partial
Expand Down Expand Up @@ -35,7 +36,7 @@

if MYPY_CHECK_RUNNING:
from optparse import Values
from typing import Any, List, Optional, Tuple
from typing import Any, Dict, List, Optional, Tuple

from pip._internal.cache import WheelCache
from pip._internal.models.target_python import TargetPython
Expand Down Expand Up @@ -76,6 +77,21 @@ def _get_index_urls(cls, options):
# Return None rather than an empty list
return index_urls or None

@classmethod
def _get_extra_headers(cls, options):
# type: (Values) -> Optional[Dict[str, str]]
"""
Return a dict of extra HTTP request headers from user-provided options.
"""
extra_headers_text = getattr(options, 'extra_headers', None)
try:
extra_headers = json.loads(extra_headers_text)
except (TypeError, ValueError):
if extra_headers_text:
raise CommandError('Could not parse extra headers as JSON')
extra_headers = None
return extra_headers

def get_default_session(self, options):
# type: (Values) -> PipSession
"""Get a default-managed session."""
Expand All @@ -98,6 +114,7 @@ def _build_session(self, options, retries=None, timeout=None):
retries=retries if retries is not None else options.retries,
trusted_hosts=options.trusted_hosts,
index_urls=self._get_index_urls(options),
extra_headers=self._get_extra_headers(options),
)

# Handle custom ca-bundles from the user
Expand Down
5 changes: 5 additions & 0 deletions src/pip/_internal/network/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ def __init__(self, *args, **kwargs):
cache = kwargs.pop("cache", None)
trusted_hosts = kwargs.pop("trusted_hosts", []) # type: List[str]
index_urls = kwargs.pop("index_urls", None)
extra_headers = kwargs.pop("extra_headers", None)

super(PipSession, self).__init__(*args, **kwargs)

Expand All @@ -240,6 +241,10 @@ def __init__(self, *args, **kwargs):
# Attach our User Agent to the request
self.headers["User-Agent"] = user_agent()

# Attach extra headers to the request
if extra_headers:
self.headers.update(extra_headers)

# Attach our Authentication handler to the session
self.auth = MultiDomainBasicAuth(index_urls=index_urls)

Expand Down
8 changes: 8 additions & 0 deletions tests/unit/test_network_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,11 @@ def warning(self, *args, **kwargs):
actual_level, actual_message = log_records[0]
assert actual_level == 'WARNING'
assert 'is not a trusted or secure host' in actual_message

def test_extra_headers(self):
session = PipSession(extra_headers={
'Authorization':
'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0N'
'TY3ODkwIiwibmFtZSI6IlNwYW0gU3BhbSIsImlhdCI6MTUxNjIzOTAyMn0.h1'
'98Wld6h_ASlfRZN3ZftXLNkGHIdrdNpXwrEOdLO1U'})
assert 'Authorization' in session.headers