Writing Hooks ============= .. note:: Whether a hook runs before or after uploading a package is a matter of the JSON configuration file. Aside, they are identical. Hooks are a fundamental part of dput-ng. Hooks make sure the package you've prepared is actually fit to upload given the target & current profile. In general, one should implement hooks for things that the remote server would ideally check for before accepting a package. Going beyond that is OK, providing you have the user's go-ahead to do so. Remember, this isn't some sort of magical restriction to upload, most remote servers would be happy with almost anything you put there, these are simply to help reduce the time to notice big errors. Theory of Operation ------------------- Pre-upload Hooks are a simple function which is invoked with a few objects to help aid in the checking process & reduce code. Pre-upload hooks will always be run before an upload, and will be given the digested .changes object, the current profile & a way to interface with the user. Pre-upload hooks (at their core) should preform a single check (as simply as it can), and either raise a subclass of :class:`dput.exceptions.HookException` or return normally. Post-upload hooks work likewise. They are just simple hooks as well, that are slightly different to pre-upload hooks. Firstly, register as a hook by placing the plugin def in the ``hooks`` class. In the event of an error, feel free to just bail out. There's not much you can do, and throwing an error is bad form. For now. This is likely to change. How a Hook Is Invoked ------------------------ Throughout this overview, we'll be looking at the :func:`dput.hooks.checksum.validate_checksums` pre-upload hook. It's one of the most simple hooks, and demonstrates the concept very clearly. To start to understand how this all works, let's take a step back and look at how :func:`dput.hook.run_hook` invokes the hook-function. Basically, ``run_hook`` will grab all the strings in the ``hooks`` key of the profile. They are just that -- simply strings. The hook are looked up using :func:`dput.util.get_obj` (which calls :func:`dput.util.load_config` to resolve the .json definition of the hook). All hooks are declared in the ``hooks`` config class, and look something like the following:: { "description": "checksum pre-upload hook", "path": "dput.hooks.checksum.validate_checksums", "pre": true } .. note:: Use ``"pre": true`` or ``"post": true`` respectively to decide whether the hook should run prior or after uploading a package. For more on this file & how it's used, check the other ref-doc on config files: :doc:`configs` Nextly, let's take a look at the ``path`` key. ``path`` is a python-importable path to the function to invoke. Let's take a look at it a bit more closely:: >>> from dput.hooks.checksum import validate_checksums >>> validate_checksums As you can see, we've imported the target, and it is, in fact, the function that we care about. .. XXX: TODO: More better handling of small scripts which should just be put somewhere dput cares about? Now that we're clear on how we got here, let's check back with the implementation of :func:`dput.hooks.checksum.validate_checksums`:: def validate_checksums(changes, profile, interface): We're passed three objects -- the ``changes``, ``profile`` and ``interface``. The ``changes`` object is an instance of :class:`dput.changes.Changes`, pre-loaded with the target of this upload action. ``profile`` is a simple dict, with the current upload profile. ``interface`` is a subclass of :class:`dput.interface.AbstractInterface`, ready to be used to talk to the user, if something comes up. What To Do When You Find an Issue --------------------------------- During runtime, and for any reason the checker sees fit to do so, the hook may abort the upload by raising a subclass of a :class:`dput.exceptions.HookException`. In cases where the user aught to make the decision (lintian errors, etc), please **prompt** the user for what to do, rather then blindly raising the error. Remember, the user can't override a checker's failure except by disabling the checker. Moreover, never prompt for inputs directly. Use the :class:`dput.interface.AbstractInterface` interface to prompt for data in a uniform way. Don't make people disable you. Be nice. Let's take a look at our reference implementation again:: def validate_checksums(changes, profile, interface): try: changes.validate_checksums(check_hash=profile["hash"]) except ChangesFileException as e: raise HashValidationError( "Bad checksums on %s: %s" % (changes.get_filename(), e) ) As you can see, the checker verifies the hashsums, catches any Exceptions thrown by the code it uses, and raises sane error text. The Exception raised (:class:`dput.hooks.checksum.HashValidationError`) is a subclass of the expected :class:`dput.exceptions.HookException`.