Skip to content

Commit

Permalink
Include in pip's User-Agent whether it looks like pip is in CI.
Browse files Browse the repository at this point in the history
  • Loading branch information
cjerdonek committed Feb 17, 2019
1 parent 24f652b commit 14a6aaa
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 1 deletion.
2 changes: 2 additions & 0 deletions news/5499.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Include in pip's User-Agent string whether it looks like pip is running
under CI.
31 changes: 31 additions & 0 deletions src/pip/_internal/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,31 @@
logger = logging.getLogger(__name__)


# These are environment variables present when running under various
# CI systems. For each variable, some CI systems that use the variable
# are indicated. The collection was chosen so that for each of a number
# of popular systems, at least one of the environment variables is used.
# This list is used to provide some indication of and lower bound for
# CI traffic to PyPI. Thus, it is okay if the list is not comprehensive.
# For more background, see: https://github.com/pypa/pip/issues/5499
CI_ENVIRONMENT_VARIABLES = [
# Azure Pipelines
'BUILD_BUILDID',
# Jenkins
'BUILD_ID',
# AppVeyor, CircleCI, Codeship, Gitlab CI, Shippable, Travis CI
'CI',
]


def looks_like_ci():
# type: () -> bool
"""
Return whether it looks like pip is running under CI.
"""
return any(name in os.environ for name in CI_ENVIRONMENT_VARIABLES)


def user_agent():
"""
Return a string representing the user agent.
Expand Down Expand Up @@ -135,6 +160,12 @@ def user_agent():
if setuptools_version is not None:
data["setuptools_version"] = setuptools_version

# Use None rather than False so as not to give the impression that
# pip knows it is not being run under CI. Rather, it is a null or
# inconclusive result. Also, we include some value rather than no
# value to make it easier to know that the check has been run.
data["ci"] = True if looks_like_ci() else None

return "{data[installer][name]}/{data[installer][version]} {json}".format(
data=data,
json=json.dumps(data, separators=(",", ":"), sort_keys=True),
Expand Down
22 changes: 21 additions & 1 deletion tests/unit/test_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,27 @@ def _fake_session_get(*args, **kwargs):


def test_user_agent():
PipSession().headers["User-Agent"].startswith("pip/%s" % pip.__version__)
user_agent = PipSession().headers["User-Agent"]

assert user_agent.startswith("pip/%s" % pip.__version__)


@pytest.mark.parametrize('name, expected_like_ci', [
('BUILD', False),
('BUILD_BUILDID', True),
('BUILD_ID', True),
('CI', True),
])
def test_user_agent__ci(monkeypatch, name, expected_like_ci):
# Clear existing names since we can be running under an actual CI.
for ci_name in ('BUILD_BUILDID', 'BUILD_ID', 'CI'):
monkeypatch.delenv(ci_name, raising=False)

monkeypatch.setenv(name, 'true')
user_agent = PipSession().headers["User-Agent"]

assert ('"ci":true' in user_agent) == expected_like_ci
assert ('"ci":null' in user_agent) == (not expected_like_ci)


class FakeStream(object):
Expand Down

0 comments on commit 14a6aaa

Please sign in to comment.