[Python-checkins] distutils2: Hook system improvements.

tarek.ziade python-checkins at python.org
Thu Aug 19 08:34:14 CEST 2010


tarek.ziade pushed 8db1ac878831 to distutils2:

http://hg.python.org/distutils2/rev/8db1ac878831
changeset:   588:8db1ac878831
user:        ?ric Araujo <merwok at netwok.org>
date:        Sun Aug 15 06:09:25 2010 +0200
summary:     Hook system improvements.
files:       src/distutils2/command/cmd.py, src/distutils2/dist.py, src/distutils2/tests/test_dist.py

diff --git a/src/distutils2/command/cmd.py b/src/distutils2/command/cmd.py
--- a/src/distutils2/command/cmd.py
+++ b/src/distutils2/command/cmd.py
@@ -53,7 +53,7 @@
 
     # Pre and post command hooks are run just before or just after the command
     # itself. They are simple functions that receive the command instance. They
-    # should be specified as dotted strings.
+    # are specified as callable objects or dotted strings (for lazy loading).
     pre_hook = None
     post_hook = None
 
diff --git a/src/distutils2/dist.py b/src/distutils2/dist.py
--- a/src/distutils2/dist.py
+++ b/src/distutils2/dist.py
@@ -375,14 +375,13 @@
                         val = parser.get(section, opt)
                         opt = opt.replace('-', '_')
 
-                        # ... although practicality beats purity :(
+                        # Hooks use a suffix system to prevent being overriden
+                        # by a config file processed later (i.e. a hook set in
+                        # the user config file cannot be replaced by a hook
+                        # set in a project config file, unless they have the
+                        # same suffix).
                         if opt.startswith("pre_hook.") or opt.startswith("post_hook."):
                             hook_type, alias = opt.split(".")
-                            # Hooks are somewhat exceptional, as they are
-                            # gathered from many config files (not overriden as
-                            # other options).
-                            # The option_dict expects {"command": ("filename", # "value")}
-                            # so for hooks, we store only the last config file processed
                             hook_dict = opt_dict.setdefault(hook_type, (filename, {}))[1]
                             hook_dict[alias] = val
                         else:
@@ -963,12 +962,34 @@
         self.have_run[command] = 1
 
     def run_command_hooks(self, cmd_obj, hook_kind):
+        """Run hooks registered for that command and phase.
+
+        *cmd_obj* is a finalized command object; *hook_kind* is either
+        'pre_hook' or 'post_hook'.
+        """
+        if hook_kind not in ('pre_hook', 'post_hook'):
+            raise ValueError('invalid hook kind: %r' % hook_kind)
+
         hooks = getattr(cmd_obj, hook_kind)
+
         if hooks is None:
             return
-        for hook in hooks.values():
-            hook_func = resolve_name(hook)
-            hook_func(cmd_obj)
+
+        for hook in hooks.itervalues():
+            if isinstance(hook, basestring):
+                try:
+                    hook_obj = resolve_name(hook)
+                except ImportError, e:
+                    raise DistutilsModuleError(e)
+            else:
+                hook_obj = hook
+
+            if not hasattr(hook_obj, '__call__'):
+                raise DistutilsOptionError('hook %r is not callable' % hook)
+
+            log.info('running %s %s for command %s',
+                     hook_kind, hook, cmd_obj.get_command_name())
+            hook_obj(cmd_obj)
 
     # -- Distribution query methods ------------------------------------
     def has_pure_modules(self):
diff --git a/src/distutils2/tests/test_dist.py b/src/distutils2/tests/test_dist.py
--- a/src/distutils2/tests/test_dist.py
+++ b/src/distutils2/tests/test_dist.py
@@ -7,9 +7,10 @@
 import warnings
 import textwrap
 
-from distutils2.dist import Distribution, fix_help_options, DistributionMetadata
+import distutils2.dist
+from distutils2.dist import Distribution, fix_help_options
 from distutils2.command.cmd import Command
-import distutils2.dist
+from distutils2.errors import DistutilsModuleError, DistutilsOptionError
 from distutils2.tests import TESTFN, captured_stdout
 from distutils2.tests import support
 from distutils2.tests.support import unittest
@@ -24,6 +25,9 @@
     def initialize_options(self):
         self.sample_option = None
 
+    def finalize_options(self):
+        pass
+
 
 class TestDistribution(Distribution):
     """Distribution subclasses that avoids the default search for
@@ -295,11 +299,22 @@
     def test_hooks_get_run(self):
         temp_home = self.mkdtemp()
         config_file = os.path.join(temp_home, "config1.cfg")
+        hooks_module = os.path.join(temp_home, "testhooks.py")
 
-        self.write_file((temp_home, "config1.cfg"), textwrap.dedent('''
+        self.write_file(config_file, textwrap.dedent('''
             [test_dist]
-            pre-hook.test = distutils2.tests.test_dist.DistributionTestCase.log_pre_call
-            post-hook.test = distutils2.tests.test_dist.DistributionTestCase.log_post_call'''))
+            pre-hook.test = testhooks.log_pre_call
+            post-hook.test = testhooks.log_post_call'''))
+
+        self.write_file(hooks_module, textwrap.dedent('''
+        record = []
+
+        def log_pre_call(cmd):
+            record.append('pre-%s' % cmd.get_command_name())
+
+        def log_post_call(cmd):
+            record.append('post-%s' % cmd.get_command_name())
+        '''))
 
         sys.argv.extend(["--command-packages",
                          "distutils2.tests",
@@ -308,17 +323,59 @@
         cmd = d.get_command_obj("test_dist")
 
         # prepare the call recorders
-        record = []
-        DistributionTestCase.log_pre_call = staticmethod(lambda _cmd: record.append(('pre', _cmd)))
-        DistributionTestCase.log_post_call = staticmethod(lambda _cmd: record.append(('post', _cmd)))
-        test_dist.run = lambda _cmd: record.append(('run', _cmd))
-        test_dist.finalize_options = lambda _cmd: record.append(('finalize_options', _cmd))
+        sys.path.append(temp_home)
+        from testhooks import record
+
+        self.addCleanup(setattr, cmd, 'run', cmd.run)
+        self.addCleanup(setattr, cmd, 'finalize_options',
+                        cmd.finalize_options)
+
+        cmd.run = lambda: record.append('run')
+        cmd.finalize_options = lambda: record.append('finalize')
 
         d.run_command('test_dist')
-        self.assertEqual(record, [('finalize_options', cmd),
-                                  ('pre', cmd),
-                                  ('run', cmd),
-                                  ('post', cmd)])
+
+        self.assertEqual(record, ['finalize',
+                                  'pre-test_dist',
+                                  'run',
+                                  'post-test_dist'])
+
+    def test_hooks_importable(self):
+        temp_home = self.mkdtemp()
+        config_file = os.path.join(temp_home, "config1.cfg")
+
+        self.write_file(config_file, textwrap.dedent('''
+            [test_dist]
+            pre-hook.test = nonexistent.dotted.name'''))
+
+        sys.argv.extend(["--command-packages",
+                         "distutils2.tests",
+                         "test_dist"])
+
+        d = self.create_distribution([config_file])
+        cmd = d.get_command_obj("test_dist")
+        cmd.ensure_finalized()
+
+        self.assertRaises(DistutilsModuleError, d.run_command, 'test_dist')
+
+    def test_hooks_callable(self):
+        temp_home = self.mkdtemp()
+        config_file = os.path.join(temp_home, "config1.cfg")
+
+        self.write_file(config_file, textwrap.dedent('''
+            [test_dist]
+            pre-hook.test = distutils2.tests.test_dist.__doc__'''))
+
+        sys.argv.extend(["--command-packages",
+                         "distutils2.tests",
+                         "test_dist"])
+
+        d = self.create_distribution([config_file])
+        cmd = d.get_command_obj("test_dist")
+        cmd.ensure_finalized()
+
+        self.assertRaises(DistutilsOptionError, d.run_command, 'test_dist')
+
 
 class MetadataTestCase(support.TempdirManager, support.EnvironGuard,
                        unittest.TestCase):

--
Repository URL: http://hg.python.org/distutils2


More information about the Python-checkins mailing list