[Zope-Checkins] SVN: Zope/branches/2.12/ Add ability to define extra zopectl commands via setuptools entrypoints.

Wichert Akkerman wichert at wiggy.net
Thu Sep 9 08:54:45 EDT 2010


Log message for revision 116267:
  Add ability to define extra zopectl commands via setuptools entrypoints.

Changed:
  U   Zope/branches/2.12/doc/CHANGES.rst
  U   Zope/branches/2.12/doc/operation.rst
  U   Zope/branches/2.12/src/Zope2/Startup/zopectl.py

-=-
Modified: Zope/branches/2.12/doc/CHANGES.rst
===================================================================
--- Zope/branches/2.12/doc/CHANGES.rst	2010-09-09 12:54:18 UTC (rev 116266)
+++ Zope/branches/2.12/doc/CHANGES.rst	2010-09-09 12:54:45 UTC (rev 116267)
@@ -11,8 +11,12 @@
 Bugs Fixed
 ++++++++++
 
+Features Added
+++++++++++++++
 
+- Add ability to define extra zopectl commands via setuptools entrypoints.
 
+
 2.12.11 (2010-09-09)
 --------------------
 

Modified: Zope/branches/2.12/doc/operation.rst
===================================================================
--- Zope/branches/2.12/doc/operation.rst	2010-09-09 12:54:18 UTC (rev 116266)
+++ Zope/branches/2.12/doc/operation.rst	2010-09-09 12:54:45 UTC (rev 116267)
@@ -153,3 +153,40 @@
   should already be available.
 
 - See the :doc:`CHANGES` for important notes on this version of Zope.
+
+
+
+Adding extra commands to Zope
+-----------------------------
+
+It is possible to add extra commands to ``zopectl`` by defining *entry points*
+in ``setup.py``. Commands have to be put in the ``zopectl.command`` group:
+
+.. code-block:: python
+
+   setup(name="MyPackage",
+         ....
+         entry_points="""
+         [zopectl.command]
+         init_app = mypackage.commands:init_application
+         """)
+
+.. note::
+
+   Due to an implementation detail of ``zopectl`` you can not use a minus
+   character (``-``) in the command name.
+
+This adds a ``init_app`` command that can be used directly from the commandline::
+
+    bin\zopectl init_app
+
+The command must be implemented as a python callable. It will be called with
+two parameters: the Zope2 application and a tuple with all commandline
+arguments. Here is a basic example:
+
+.. code-block:: python
+
+   def init_application(app, args):
+       print 'Initialisating the application'
+
+

Modified: Zope/branches/2.12/src/Zope2/Startup/zopectl.py
===================================================================
--- Zope/branches/2.12/src/Zope2/Startup/zopectl.py	2010-09-09 12:54:18 UTC (rev 116266)
+++ Zope/branches/2.12/src/Zope2/Startup/zopectl.py	2010-09-09 12:54:45 UTC (rev 116267)
@@ -36,10 +36,13 @@
 action "help" to find out about available actions.
 """
 
+import csv
 import os
 import sys
 import signal
 
+import pkg_resources
+
 import zdaemon
 import Zope2.Startup
 
@@ -317,6 +320,63 @@
         print "debug -- run the Zope debugger to inspect your database"
         print "         manually using a Python interactive shell"
 
+    def __getattr__(self, name):
+        """Getter to check if an unknown command is implement by an entry point."""
+        if not name.startswith("do_"):
+            raise AttributeError(name)
+        data=list(pkg_resources.iter_entry_points("zopectl.command", name=name[3:]))
+        if not data:
+            raise AttributeError(name)
+        if len(data)>1:
+            print >>sys.stderr, "Warning: multiple entry points found for command"
+            return
+        func=data[0].load()
+        if not callable(func):
+            print >>sys.stderr, "Error: %s is not a callable method" % name
+            return
+
+        return self.run_entrypoint(data[0])
+
+
+    def run_entrypoint(self, entry_point):
+        def go(arg):
+            # If the command line was something like
+            # """bin/instance run "one two" three"""
+            # cmd.parseline will have converted it so
+            # that arg == 'one two three'. This is going to
+            # foul up any quoted command with embedded spaces.
+            # So we have to return to self.options.args,
+            # which is a tuple of command line args,
+            # throwing away the "run" command at the beginning.
+            #
+            # Further complications: if self.options.args has come
+            # via subprocess, it may look like
+            # ['run "arg 1" "arg2"'] rather than ['run','arg 1','arg2'].
+            # If that's the case, we'll use csv to do the parsing
+            # so that we can split on spaces while respecting quotes.
+            if len(self.options.args) == 1:
+                tup = csv.reader(self.options.args, delimiter=' ').next()
+
+            # Remove -c and add command name as sys.argv[0]
+            cmd = [ 'import sys',
+                    'sys.argv.pop()',
+                    'sys.argv.append(r\'%s\')' % entry_point.name
+                   ]
+            if len(tup) > 1:
+                argv = tup[1:]
+                cmd.append('[sys.argv.append(x) for x in %s]; ' % argv)
+            cmd.extend([
+                'import pkg_resources',
+                'import Zope2',
+                'func=pkg_resources.EntryPoint.parse(\'%s\').load(False)' % entry_point,
+                'app=Zope2.app()',
+                'func(app)',
+                ])
+            cmdline = self.get_startup_cmd(self.options.python, ' ; '.join(cmd))
+            self._exitstatus = os.system(cmdline)
+        return go
+
+
     def do_run(self, args):
         if not args:
             print "usage: run <script> [args]"



More information about the Zope-Checkins mailing list