Getting the correct file path on case-insensitive systems

On such systems like Windows and macOS, is there a way to get the path as stored on disk without resolving symbolic links? Context is that within a virtual environment the paths that sysconfig returns are in all caps for some reason:

cc @barneygale the current path expert :slightly_smiling_face:

What do listdir and other such functions return? Do they return the actual case?

If they do, then that’s one way.

It does return the actual case but that means I would have to do that for every path component.

A quick look reveals os.path.realpath:

>>> os.path.realpath('C:/PYTHON312')

Yes that definitely works but I’m trying to find a way without resolving symbolic links like I mentioned.

Is this a windows only problem? If so symlinks are not an issue?
So you could call os.path.realpath only is on Windows?

This also affects macOS. I don’t want to resolve because it’s possible that an input path must be a symbolic link, I just want the actual name.

It feels like this must be happening in get_paths, maybe environment variables are being changed in the venv? Or it thinks the OS is different for some reason…

It doesn’t happen in my env but i’m not using hatch.

On macOS with python 3.11 I cannot reproduce this problem:

% python3.11
Python 3.11.5 (v3.11.5:cce6ba91b3, Aug 24 2023, 10:50:31) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sysconfig
>>> print(sysconfig.get_path('include'))

Also I cannot reproduce on windows 11.

py -3.11
Python 3.11.6 (tags/v3.11.6:8b6ee5b, Oct  2 2023, 14:57:12) [MSC v.1935 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sysconfig
>>> print(sysconfig.get_path('include'))
C:\Program Files\Python311\Include

Also works when I create a venv.

Something your code is doing to the value?

Still, I would like to know if there is a way to get the correct path name without resolving symbolic links.

For each element in the path you would need to os.listdir() and pick the matching entry.

1 Like

One way to do it:

def realcase(path):
    dirname, basename = os.path.split(path)
    if dirname == path:
        return dirname
    dirname = realcase(dirname)
    basename = os.path.normcase(basename)
    for child in os.listdir(dirname):
        if os.path.normcase(child) == basename:
            return os.path.join(dirname, child)
    raise FileNotFoundError(f'{path} not found')
1 Like