Skip to content

site.getusersitepackages returning wrong path for Python Install Manager installs #149328

@AGraber

Description

@AGraber

Bug report

Bug description:

On Python installations managed by Python Install Manager, doing python -m site will usually output something similar to this:

sys.path = [
    'C:\\Users\\<username>',
    'C:\\Users\\<username>\\AppData\\Local\\Python\\pythoncore-3.14-64\\python314.zip',
    'C:\\Users\\<username>\\AppData\\Local\\Python\\pythoncore-3.14-64\\DLLs',
    'C:\\Users\\<username>\\AppData\\Local\\Python\\pythoncore-3.14-64\\Lib',
    'C:\\Users\\<username>\\AppData\\Local\\Python\\bin',
    'C:\\Users\\<username>\\AppData\\Local\\Python\\pythoncore-3.14-64',
    'C:\\Users\\<username>\\AppData\\Local\\Python\\pythoncore-3.14-64\\Lib\\site-packages',
]
USER_BASE: 'C:\\Users\\<username>\\AppData\\Roaming\\Python' (doesn't exist)
USER_SITE: 'C:\\Users\\<username>\\AppData\\Roaming\\Python\\Python314\\site-packages' (doesn't exist)
ENABLE_USER_SITE: True

The path for USER_BASE and USER_SITE reference locations that are usually the ones used when a standalone Python Installer is used, but they don't exist nor are used when Python is managed by Python Install Manager.

These paths comes from this code, in particular the == 'nt' bits:

cpython/Lib/site.py

Lines 466 to 506 in 24c4aec

def _getuserbase():
env_base = os.environ.get("PYTHONUSERBASE", None)
if env_base:
return env_base
# Emscripten, iOS, tvOS, VxWorks, WASI, and watchOS have no home directories
if sys.platform in {"emscripten", "ios", "tvos", "vxworks", "wasi", "watchos"}:
return None
def joinuser(*args):
return os.path.expanduser(os.path.join(*args))
if os.name == "nt":
base = os.environ.get("APPDATA") or "~"
return joinuser(base, _get_implementation())
if sys.platform == "darwin" and sys._framework:
return joinuser("~", "Library", sys._framework,
"%d.%d" % sys.version_info[:2])
return joinuser("~", ".local")
# Same to sysconfig.get_path('purelib', os.name+'_user')
def _get_path(userbase):
version = sys.version_info
if hasattr(sys, 'abiflags') and 't' in sys.abiflags:
abi_thread = 't'
else:
abi_thread = ''
implementation = _get_implementation()
implementation_lower = implementation.lower()
if os.name == 'nt':
ver_nodot = sys.winver.replace('.', '')
return f'{userbase}\\{implementation}{ver_nodot}\\site-packages'
if sys.platform == 'darwin' and sys._framework:
return f'{userbase}/lib/{implementation_lower}/site-packages'
return f'{userbase}/lib/{implementation_lower}{version[0]}.{version[1]}{abi_thread}/site-packages'

The assumptions made by this code largely diverge from what Python Install Manager uses, so functions like site.getusersitepackages() will consistently return a path that is irrelevant.

For now, this isn't a problem when importing packages because sys.path will be followed anyways, but it breaks code that relies on site.getusersitepackages() to provide a correct path.

I think this should be rectified so that it points to the correct path managed by Python Install Manager (in this scenario, it would ideally return C:\Users\<username>\AppData\Local\Python\pythoncore-3.14-64\Lib\site-packages), because, as described by PEP 773:

The existing .exe installer and py launcher will be deprecated and no
longer released from two years after this PEP is accepted.

This means that this code will almost always return an non existent path on Windows installations due to the preferred way of installing Python being via Python Install Manager (without considering other python version managers).

Minimal Repro:

import site
print(site.getusersitepackages()) # Will print something like 'C:\\Users\\<username>\\AppData\\Roaming\\Python\\Python314' even when using Python Install Manager

Furthermore, this might be a bit off-topic, but I was concerned that there was no way to override the value returned by e.g. site.getusersitepackages(). There is no workaround that will get that function to return a value based on, for example, environmental variables, which would've been enough to workaround this issue, by setting an environmental variable to point to the correct path. You can indeed steer it a little bit via PYTHONUSERBASE, but this is not enough because the rest of the path can't be influenced.

CPython versions tested on:

3.14

Operating systems tested on:

Windows

Metadata

Metadata

Assignees

No one assigned

    Labels

    OS-windowsstdlibStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancement

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions