简体   繁体   中英

On OS X, is it possible to get the user library directory from pure Python?

I'm writing a pure Python library, and for various reasons I would very much like to avoid asking users to install any binary extensions. However, when running on OS X, I would also like to locate the user library directory ( ~/Library ) so I can store some configuration data there, and my understanding is that for Very Valid And Vague But Important Reasons the proper way to do this is not by just writing ~/Library in my code, but instead by asking OS X where the directory is with some code like

[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
                                     NSUserDomainMask,
                                     YES)
 objectAtIndex:0];

Of course, this code is Objective-C, not Python, so I can't just use it directly. And if it were plain C, I'd just use ctypes to call it from Python, but it isn't. Is there any way to make this call from Python, without writing an extension module in Objective-C or requiring the user to install some extension module like PyObjC ? Alternatively, if I just give up and hard-code ~/Library like everyone else does, then will anything terrible happen?

Well, it is plain C under the hood, so you can achieve the same result with ctypes module:

from ctypes import *

NSLibraryDirectory = 5
NSUserDomainMask = 1

def NSSearchPathForDirectoriesInDomains(directory, domainMask, expand = True):
    # If library path looks like framework, OS X will search $DYLD_FRAMEWORK_PATHs automatically
    # There's no need to specify full path (/System/Library/Frameworks/...)
    Foundation = cdll.LoadLibrary("Foundation.framework/Foundation")
    CoreFoundation = cdll.LoadLibrary("CoreFoundation.framework/CoreFoundation");

    _NSSearchPathForDirectoriesInDomains = Foundation.NSSearchPathForDirectoriesInDomains
    _NSSearchPathForDirectoriesInDomains.argtypes = [ c_uint, c_uint, c_bool ]
    _NSSearchPathForDirectoriesInDomains.restype = c_void_p

    _CFRelease = CoreFoundation.CFRelease
    _CFRelease.argtypes = [ c_void_p ]

    _CFArrayGetCount = CoreFoundation.CFArrayGetCount
    _CFArrayGetCount.argtypes = [ c_void_p ]
    _CFArrayGetCount.restype = c_uint

    _CFArrayGetValueAtIndex = CoreFoundation.CFArrayGetValueAtIndex
    _CFArrayGetValueAtIndex.argtypes = [ c_void_p, c_uint ]
    _CFArrayGetValueAtIndex.restype = c_void_p

    _CFStringGetCString = CoreFoundation.CFStringGetCString
    _CFStringGetCString.argtypes = [ c_void_p, c_char_p, c_uint, c_uint ]
    _CFStringGetCString.restype = c_bool

    kCFStringEncodingUTF8 = 0x08000100

    # MAX_PATH on POSIX is usually 4096, so it should be enough
    # It might be determined dynamically, but don't bother for now
    MAX_PATH = 4096

    result = []

    paths = _NSSearchPathForDirectoriesInDomains(directory, domainMask, expand)

    # CFArrayGetCount will crash if argument is NULL
    # Even though NSSearchPathForDirectoriesInDomains never returns null, we'd better check it
    if paths:
        for i in range(0, _CFArrayGetCount(paths)):
            path = _CFArrayGetValueAtIndex(paths, i)

            buff = create_string_buffer(MAX_PATH)
            if _CFStringGetCString(path, buff, sizeof(buff), kCFStringEncodingUTF8):
                result.append(buff.raw.decode('utf-8').rstrip('\0'))
            del buff

        _CFRelease(paths)

    return result

print NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask)

But the universe probably won't collapse if you just use ~/Library ;)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM