]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Add importlib.util.resolve_name().
authorBrett Cannon <brett@python.org>
Sun, 13 May 2012 17:45:09 +0000 (13:45 -0400)
committerBrett Cannon <brett@python.org>
Sun, 13 May 2012 17:45:09 +0000 (13:45 -0400)
Doc/library/importlib.rst
Lib/importlib/test/test_util.py
Lib/importlib/util.py
Misc/NEWS

index 0bc1b65a4b6a1e69fb4416fc30b88bb7494af2be..35a99bf0f5e783cdf98d9b18b59465829c00f80c 100644 (file)
@@ -737,6 +737,22 @@ find and load modules.
 This module contains the various objects that help in the construction of
 an :term:`importer`.
 
+.. function:: resolve_name(name, package)
+
+   Resolve a relative module name to an absolute one.
+
+   If  **name** has no leading dots, then **name** is simply returned. This
+   allows for usage such as
+   ``importlib.util.resolve_name('sys', __package__)`` without doing a
+   check to see if the **package** argument is needed.
+
+   :exc:`ValueError` is raised if **name** is a relative module name but
+   package is a false value (e.g. ``None`` or the empty string).
+   :exc:`ValueError` is also raised a relative name would escape its containing
+   package (e.g. requesting ``..bacon`` from within the ``spam`` package).
+
+   .. versionadded:: 3.3
+
 .. decorator:: module_for_loader
 
     A :term:`decorator` for a :term:`loader` method,
index 7963e4fa1eefa442ee6a3cb031238025d1d65a58..e477f171f0cc04cada5929f523ec36cf9b170f77 100644 (file)
@@ -161,9 +161,47 @@ class SetPackageTests(unittest.TestCase):
         self.assertEqual(wrapped.__name__, fxn.__name__)
         self.assertEqual(wrapped.__qualname__, fxn.__qualname__)
 
+
+class ResolveNameTests(unittest.TestCase):
+
+    """Tests importlib.util.resolve_name()."""
+
+    def test_absolute(self):
+        # bacon
+        self.assertEqual('bacon', util.resolve_name('bacon', None))
+
+    def test_aboslute_within_package(self):
+        # bacon in spam
+        self.assertEqual('bacon', util.resolve_name('bacon', 'spam'))
+
+    def test_no_package(self):
+        # .bacon in ''
+        with self.assertRaises(ValueError):
+            util.resolve_name('.bacon', '')
+
+    def test_in_package(self):
+        # .bacon in spam
+        self.assertEqual('spam.eggs.bacon',
+                         util.resolve_name('.bacon', 'spam.eggs'))
+
+    def test_other_package(self):
+        # ..bacon in spam.bacon
+        self.assertEqual('spam.bacon',
+                         util.resolve_name('..bacon', 'spam.eggs'))
+
+    def test_escape(self):
+        # ..bacon in spam
+        with self.assertRaises(ValueError):
+            util.resolve_name('..bacon', 'spam')
+
+
 def test_main():
     from test import support
-    support.run_unittest(ModuleForLoaderTests, SetPackageTests)
+    support.run_unittest(
+            ModuleForLoaderTests,
+            SetPackageTests,
+            ResolveNameTests
+        )
 
 
 if __name__ == '__main__':
index 7b44fa1344c1e0a368830fde099408e3a77ee947..13164371025404591a62b1a58bcaac937d6aee0d 100644 (file)
@@ -3,3 +3,19 @@
 from ._bootstrap import module_for_loader
 from ._bootstrap import set_loader
 from ._bootstrap import set_package
+from ._bootstrap import _resolve_name
+
+
+def resolve_name(name, package):
+    """Resolve a relative module name to an absolute one."""
+    if not name.startswith('.'):
+        return name
+    elif not package:
+        raise ValueError('{!r} is not a relative name '
+                         '(no leading dot)'.format(name))
+    level = 0
+    for character in name:
+        if character != '.':
+            break
+        level += 1
+    return _resolve_name(name[level:], package, level)
index 5458385a8044d441c11bfefca9dab62abfd258e6..9adce9f6307000b0898f67220297180c4668c8e0 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -23,6 +23,8 @@ Core and Builtins
 Library
 -------
 
+- Add importlib.util.resolve_name().
+
 - Issue #14366: Support lzma compression in zip files.
   Patch by Serhiy Storchaka.