From d200bf534b6d97ee607e1071d0cb2d93e4506268 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Sun, 13 May 2012 13:45:09 -0400 Subject: Add importlib.util.resolve_name(). --- Doc/library/importlib.rst | 16 ++++++++++++++++ Lib/importlib/test/test_util.py | 40 +++++++++++++++++++++++++++++++++++++++- Lib/importlib/util.py | 16 ++++++++++++++++ Misc/NEWS | 2 ++ 4 files changed, 73 insertions(+), 1 deletion(-) diff --git a/Doc/library/importlib.rst b/Doc/library/importlib.rst index 0bc1b65..35a99bf 100644 --- a/Doc/library/importlib.rst +++ b/Doc/library/importlib.rst @@ -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, diff --git a/Lib/importlib/test/test_util.py b/Lib/importlib/test/test_util.py index 7963e4f..e477f17 100644 --- a/Lib/importlib/test/test_util.py +++ b/Lib/importlib/test/test_util.py @@ -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__': diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index 7b44fa1..1316437 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -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) diff --git a/Misc/NEWS b/Misc/NEWS index 5458385..9adce9f 100644 --- 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. -- cgit v0.12