Skip to content

Commit ecbe8ec

Browse files
committed
Basic structure of indexurl
1 parent a876054 commit ecbe8ec

File tree

6 files changed

+161
-2
lines changed

6 files changed

+161
-2
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# indexurl
22

3+
This is a super-simple project to read the global.index-url config value of pip
4+
without extra processes.
35

6+
```py
7+
from indexurl import get_index_url
8+
print(get_index_url())
9+
```
410

511
# License
612

indexurl/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""
2+
This is a trivial package to find a likely index_url given current system
3+
configuration.
4+
"""
5+
6+
from .core import get_index_url
7+
8+
__all__ = ["get_index_url"]

indexurl/core.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import logging
2+
import os
3+
from configparser import RawConfigParser
4+
from pathlib import Path
5+
from typing import Optional
6+
7+
import appdirs
8+
9+
LOG = logging.getLogger(__name__)
10+
11+
DEFAULT_INDEX_URL = "https://pypi.org/simple"
12+
13+
14+
def get_index_url() -> str:
15+
"""
16+
Returns the configured (or default) value of global.index-url
17+
18+
For most intents this matches the value of `pip config get global.index-url`
19+
except it works with older versions of pip, has the default in a common
20+
place, normalizes the trailing slash away, and doesn't require another
21+
process or pip to be installed.
22+
"""
23+
# There are a lot of places this can live.
24+
# See https://pip.pypa.io/en/stable/topics/configuration/#location
25+
#
26+
# Note that we look at them in _reverse_ order so we can stop as soon as the
27+
# key is found.
28+
29+
virtual_env = os.getenv("VIRTUAL_ENV")
30+
pip_config_file = os.getenv("PIP_CONFIG_FILE")
31+
xdg_config_dirs = os.getenv("XDG_CONFIG_DIRS", "").split(",")
32+
if pip_config_file == "os.devnull":
33+
return DEFAULT_INDEX_URL
34+
35+
for option in [
36+
# Site
37+
Path(virtual_env, "pip.conf") if virtual_env else None,
38+
# User
39+
Path(appdirs.user_config_dir("pip"), "pip.conf"), # omit some Mac logic
40+
# Global
41+
Path("/etc/pip.conf"),
42+
*(Path(d, "pip", "pip.conf") for d in xdg_config_dirs),
43+
# Env
44+
Path(pip_config_file) if pip_config_file else None,
45+
]:
46+
if option is not None:
47+
index_url = _get_global_index_url_from_file(option)
48+
if index_url:
49+
return index_url.rstrip("/")
50+
51+
return DEFAULT_INDEX_URL
52+
53+
54+
def _get_global_index_url_from_file(path: Path) -> Optional[str]:
55+
"""
56+
Returns the global.index-url, or None. Logs an error message if file exists
57+
but is invalid or unreadable.
58+
"""
59+
if not path.exists():
60+
return None
61+
config = RawConfigParser()
62+
try:
63+
config.read(path)
64+
except Exception as e:
65+
LOG.warning("Config %s could not be read: %s", path, repr(e))
66+
return config.get("global", "index-url", fallback=None)
67+
68+
69+
if __name__ == "__main__": # pragma: no cover
70+
print(get_index_url())

indexurl/tests/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
# from .foo import FooTest
1+
from .core import IndexUrlTest
22

33
__all__ = [
4-
# "FooTest",
4+
"IndexUrlTest",
55
]

indexurl/tests/core.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import os
2+
import tempfile
3+
import unittest
4+
from contextlib import contextmanager
5+
from pathlib import Path
6+
from typing import Generator
7+
8+
from ..core import _get_global_index_url_from_file, get_index_url
9+
10+
11+
@contextmanager
12+
def patch_env(var: str, value: str) -> Generator[None, None, None]:
13+
old_value = os.getenv(var)
14+
os.environ[var] = value
15+
yield
16+
if old_value is None:
17+
del os.environ[var]
18+
else:
19+
os.environ[var] = old_value
20+
21+
22+
class IndexUrlTest(unittest.TestCase):
23+
def test_basic_reading(self) -> None:
24+
with tempfile.TemporaryDirectory() as d:
25+
p = Path(d, "a.ini")
26+
self.assertEqual(None, _get_global_index_url_from_file(p))
27+
p.write_text("")
28+
self.assertEqual(None, _get_global_index_url_from_file(p))
29+
p.write_text("[global]\n")
30+
self.assertEqual(None, _get_global_index_url_from_file(p))
31+
p.write_text("[global]\nfoo=a\n")
32+
self.assertEqual(None, _get_global_index_url_from_file(p))
33+
34+
p.write_text("[global]\nindex-url=a")
35+
self.assertEqual("a", _get_global_index_url_from_file(p))
36+
p.write_text("[global]\nindex-url = a\n")
37+
self.assertEqual("a", _get_global_index_url_from_file(p))
38+
p.write_text("[global]\nindex-url = a\r\n")
39+
self.assertEqual("a", _get_global_index_url_from_file(p))
40+
p.write_text("]")
41+
self.assertEqual(None, _get_global_index_url_from_file(p))
42+
43+
def test_pip_config_file(self) -> None:
44+
with tempfile.TemporaryDirectory() as d:
45+
Path(d, "pip.conf").write_text("[global]\nindex-url=a\n")
46+
Path(d, "pip.t").write_text("[global]\nindex-url=t\n")
47+
with patch_env("PIP_CONFIG_FILE", str(Path(d, "pip.t"))):
48+
self.assertEqual("t", get_index_url())
49+
with patch_env("VIRTUAL_ENV", d):
50+
self.assertEqual("a", get_index_url())
51+
with patch_env("PIP_CONFIG_FILE", "os.devnull"), patch_env(
52+
"VIRTUAL_ENV", d
53+
):
54+
self.assertEqual("https://pypi.org/simple", get_index_url())
55+
56+
def test_virtual_env(self) -> None:
57+
with tempfile.TemporaryDirectory() as d:
58+
Path(d, "pip.conf").write_text("[global]\nindex-url=a\n")
59+
with patch_env("VIRTUAL_ENV", d):
60+
self.assertEqual("a", get_index_url())
61+
62+
# This one only makes sense on *nix
63+
def test_xdg_config_dirs(self) -> None:
64+
with tempfile.TemporaryDirectory() as d:
65+
with patch_env("XDG_CONFIG_DIRS", f"{d},"):
66+
Path(d, "pip").mkdir()
67+
Path(d, "pip", "pip.conf").write_text("[global]\nindex-url=a\n")
68+
self.assertEqual("a", get_index_url())
69+
70+
def test_fallback(self) -> None:
71+
with patch_env("VIRTUAL_ENV", ""), patch_env("XDG_CONFIG_DIRS", ""), patch_env(
72+
"HOME", ""
73+
), patch_env("XDG_CONFIG_HOME", ""), patch_env("PIP_CONFIG_FILE", ""):
74+
self.assertEqual("https://pypi.org/simple", get_index_url())

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ setup_requires =
1616
python_requires = >=3.10
1717
include_package_data = true
1818
install_requires =
19+
appdirs >= 1.3.0
1920

2021
[options.extras_require]
2122
dev =

0 commit comments

Comments
 (0)