class TaskCreatorMetaclass(type): # create_doit_tasks is expected to take no arguments when doit calls it. # Still, we want to be able to access self while tasks are being created. # # If an instance of a class with a create_doit_tasks function is called # via dot notation, _as is presently done in doit_, Python's descriptor # protocol applies; the instance's call of create_doit_task will be # converted into a bound method with implicit self (i.e. the normal case # with dot notation calling an instance method, where the self parameter # becomes a hidden first argument). # # Doit looks for _any_ objects in scope with a create_doit_tasks method # when loading tasks from a module; _this includes classes, which are also # object/instances (with a type of some sort of metaclass)!_. However, for # the class definitions, the descriptor protocol doesn't kick in. This is # because descriptors only kick in for class attributes, and From the POV # of the class definitions, create_doit_task is an attribute on the class # object _instance_, not a class attribute. So when doit tries to call # create_doit_task on a class object, create_doit_tasks is expecting a self # argument that wasn't provided. # # Tweak class creation so that the create_doit_tasks attribute is hidden # from doit and thus doit skips looking for tasks from the class objects. def __getattribute__(self, name: str): if name in ("create_doit_tasks",): raise AttributeError else: return super().__getattribute__(name) class ZfsList(metaclass=TaskCreatorMetaclass): def __init__(self, config): self.config = config super().__init__() def build_cmd(self, pool, book): return f"zfs list -H -rtsnap{',bookmark' if book else ''} -o name {pool}" def create_doit_tasks(self): # We run zfs list several times- sometimes the same command!- to reduce the # probability of TOCTOU problems. yield { "name": "pre_create", "actions": [CmdAction(self.build_cmd(self.config.from_, True), save_out="snapbooks_raw")], "verbosity": 0, } yield { "name": "pre_prepare", "actions": [CmdAction(self.build_cmd(self.config.to, False), save_out="snaps_raw")], "verbosity": 0, } yield { "name": "pre_send_from", "actions": [CmdAction(self.build_cmd(self.config.from_, True), save_out="from_raw")], "verbosity": 0, } yield { "name": "pre_send_to", "actions": [CmdAction(self.build_cmd(self.config.to, False), save_out="to_raw")], "verbosity": 0, }