diff --git a/Doc/library/site.rst b/Doc/library/site.rst index 0fe63a3a4d0..82184cbf3d0 100644 --- a/Doc/library/site.rst +++ b/Doc/library/site.rst @@ -116,6 +116,32 @@ empty, and the path manipulations are skipped; however the import of Adds a directory to sys.path and processes its pth files. +.. function:: getsitepackages() + + Returns a list containing all global site-packages directories + (and possibly site-python). + + .. versionadded:: 2.7 + +.. function:: getuserbase() + + Returns the `user base` directory path. + + The `user base` directory can be used to store data. If the global + variable ``USER_BASE`` is not initialized yet, this function will also set + it. + + .. versionadded:: 2.7 + +.. function:: getusersitepackages() + + Returns the user-specific site-packages directory path. + + If the global variable ``USER_SITE`` is not initialized yet, this + function will also set it. + + .. versionadded:: 2.7 XXX Update documentation XXX document python -m site --user-base --user-site + diff --git a/Lib/site.py b/Lib/site.py index 7bbd962632b..3d455e79e23 100644 --- a/Lib/site.py +++ b/Lib/site.py @@ -61,7 +61,10 @@ PREFIXES = [sys.prefix, sys.exec_prefix] # Enable per user site-packages directory # set it to False to disable the feature or True to force the feature ENABLE_USER_SITE = None + # for distutils.commands.install +# These values are initialized by the getuserbase() and getusersitepackages() +# functions, through the main() function when Python starts. USER_SITE = None USER_BASE = None @@ -205,49 +208,75 @@ def check_enableusersite(): return True +def getuserbase(): + """Returns the `user base` directory path. + + The `user base` directory can be used to store data. If the global + variable ``USER_BASE`` is not initialized yet, this function will also set + it. + """ + global USER_BASE + if USER_BASE is not None: + return USER_BASE + + env_base = os.environ.get("PYTHONUSERBASE", None) + + def joinuser(*args): + return os.path.expanduser(os.path.join(*args)) + + # what about 'os2emx', 'riscos' ? + if os.name == "nt": + base = os.environ.get("APPDATA") or "~" + USER_BASE = env_base if env_base else joinuser(base, "Python") + else: + USER_BASE = env_base if env_base else joinuser("~", ".local") + + return USER_BASE + +def getusersitepackages(): + """Returns the user-specific site-packages directory path. + + If the global variable ``USER_SITE`` is not initialized yet, this + function will also set it. + """ + global USER_SITE + user_base = getuserbase() # this will also set USER_BASE + + if USER_SITE is not None: + return USER_SITE + + if os.name == "nt": + USER_SITE = os.path.join(user_base, "Python" + sys.version[0] + + sys.version[2], "site-packages") + else: + USER_SITE = os.path.join(user_base, "lib", "python" + sys.version[:3], + "site-packages") + + return USER_SITE def addusersitepackages(known_paths): """Add a per user site-package to sys.path Each user has its own python directory with site-packages in the home directory. - - USER_BASE is the root directory for all Python versions - - USER_SITE is the user specific site-packages directory - - USER_SITE/.. can be used for data. """ - global USER_BASE, USER_SITE, ENABLE_USER_SITE - env_base = os.environ.get("PYTHONUSERBASE", None) + # get the per user site-package path + # this call will also make sure USER_BASE and USER_SITE are set + user_site = getusersitepackages() - def joinuser(*args): - return os.path.expanduser(os.path.join(*args)) - - #if sys.platform in ('os2emx', 'riscos'): - # # Don't know what to put here - # USER_BASE = '' - # USER_SITE = '' - if os.name == "nt": - base = os.environ.get("APPDATA") or "~" - USER_BASE = env_base if env_base else joinuser(base, "Python") - USER_SITE = os.path.join(USER_BASE, - "Python" + sys.version[0] + sys.version[2], - "site-packages") - else: - USER_BASE = env_base if env_base else joinuser("~", ".local") - USER_SITE = os.path.join(USER_BASE, "lib", - "python" + sys.version[:3], - "site-packages") - - if ENABLE_USER_SITE and os.path.isdir(USER_SITE): - addsitedir(USER_SITE, known_paths) + if ENABLE_USER_SITE and os.path.isdir(user_site): + addsitedir(user_site, known_paths) return known_paths +def getsitepackages(): + """Returns a list containing all global site-packages directories + (and possibly site-python). -def addsitepackages(known_paths): - """Add site-packages (and possibly site-python) to sys.path""" - sitedirs = [] + For each directory present in the global ``PREFIXES``, this function + will find its `site-packages` subdirectory depending on the system + environment, and will return a list of full paths. + """ + sitepackages = [] seen = [] for prefix in PREFIXES: @@ -256,35 +285,36 @@ def addsitepackages(known_paths): seen.append(prefix) if sys.platform in ('os2emx', 'riscos'): - sitedirs.append(os.path.join(prefix, "Lib", "site-packages")) + sitepackages.append(os.path.join(prefix, "Lib", "site-packages")) elif os.sep == '/': - sitedirs.append(os.path.join(prefix, "lib", + sitepackages.append(os.path.join(prefix, "lib", "python" + sys.version[:3], "site-packages")) - sitedirs.append(os.path.join(prefix, "lib", "site-python")) + sitepackages.append(os.path.join(prefix, "lib", "site-python")) else: - sitedirs.append(prefix) - sitedirs.append(os.path.join(prefix, "lib", "site-packages")) - + sitepackages.append(prefix) + sitepackages.append(os.path.join(prefix, "lib", "site-packages")) if sys.platform == "darwin": # for framework builds *only* we add the standard Apple # locations. if 'Python.framework' in prefix: - sitedirs.append( + sitepackages.append( os.path.expanduser( os.path.join("~", "Library", "Python", sys.version[:3], "site-packages"))) - sitedirs.append( + sitepackages.append( os.path.join("/Library", "Python", sys.version[:3], "site-packages")) + return sitepackages - for sitedir in sitedirs: +def addsitepackages(known_paths): + """Add site-packages (and possibly site-python) to sys.path""" + for sitedir in getsitepackages(): if os.path.isdir(sitedir): addsitedir(sitedir, known_paths) return known_paths - def setBEGINLIBPATH(): """The OS/2 EMX port has optional extension modules that do double duty as DLLs (and must use the .DLL file extension) for other extensions. diff --git a/Lib/test/test_site.py b/Lib/test/test_site.py index 7ed0ee24d48..20444b76247 100644 --- a/Lib/test/test_site.py +++ b/Lib/test/test_site.py @@ -35,10 +35,16 @@ class HelperFunctionsTests(unittest.TestCase): def setUp(self): """Save a copy of sys.path""" self.sys_path = sys.path[:] + self.old_base = site.USER_BASE + self.old_site = site.USER_SITE + self.old_prefixes = site.PREFIXES def tearDown(self): """Restore sys.path""" sys.path = self.sys_path + site.USER_BASE = self.old_base + site.USER_SITE = self.old_site + site.PREFIXES = self.old_prefixes def test_makepath(self): # Test makepath() have an absolute path for its first return value @@ -122,6 +128,60 @@ class HelperFunctionsTests(unittest.TestCase): env=env) self.assertEqual(rc, 1) + def test_getuserbase(self): + site.USER_BASE = None + user_base = site.getuserbase() + + # the call sets site.USER_BASE + self.assertEquals(site.USER_BASE, user_base) + + # let's set PYTHONUSERBASE and see if it uses it + site.USER_BASE = None + with EnvironmentVarGuard() as environ: + environ['PYTHONUSERBASE'] = 'xoxo' + self.assertTrue(site.getuserbase().startswith('xoxo')) + + def test_getusersitepackages(self): + site.USER_SITE = None + site.USER_BASE = None + user_site = site.getusersitepackages() + + # the call sets USER_BASE *and* USER_SITE + self.assertEquals(site.USER_SITE, user_site) + self.assertTrue(user_site.startswith(site.USER_BASE)) + + def test_getsitepackages(self): + site.PREFIXES = ['xoxo'] + dirs = site.getsitepackages() + + if sys.platform in ('os2emx', 'riscos'): + self.assertTrue(len(dirs), 1) + wanted = os.path.join('xoxo', 'Lib', 'site-packages') + self.assertEquals(dirs[0], wanted) + elif os.sep == '/': + self.assertTrue(len(dirs), 2) + wanted = os.path.join('xoxo', 'lib', 'python' + sys.version[:3], + 'site-packages') + self.assertEquals(dirs[0], wanted) + wanted = os.path.join('xoxo', 'lib', 'site-python') + self.assertEquals(dirs[1], wanted) + else: + self.assertTrue(len(dirs), 2) + self.assertEquals(dirs[0], 'xoxo') + wanted = os.path.join('xoxo', 'Lib', 'site-packages') + self.assertEquals(dirs[1], wanted) + + # let's try the specific Apple location + if sys.platform == "darwin": + site.PREFIXES = ['Python.framework'] + dirs = site.getsitepackages() + self.assertTrue(len(dirs), 4) + wanted = os.path.join('~', 'Library', 'Python', + sys.version[:3], 'site-packages') + self.assertEquals(dirs[2], os.path.expanduser(wanted)) + wanted = os.path.join('/Library', 'Python', sys.version[:3], + 'site-packages') + self.assertEquals(dirs[3], wanted) class PthFile(object): """Helper class for handling testing of .pth files""" diff --git a/Misc/NEWS b/Misc/NEWS index 9b38d34e7d5..c9717417341 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -992,6 +992,8 @@ Core and Builtins Library ------- +- Issue #6693: New functions in site.py to get user/global site packages paths. + - Issue #6511: ZipFile now raises BadZipfile (instead of an IOError) when opening an empty or very small file.