pydoit / doit

CLI task management & automation tool
http://pydoit.org
MIT License
1.87k stars 175 forks source link

[low priority] BaseAction utility methods - __eq__, multi-create #303

Closed floer32 closed 5 years ago

floer32 commented 5 years ago

I found these useful. What do you think about if I cleaned them up and made a PR? Would be Py2 compatible of course, though examples are from Py3 (f-strings etc would be changed).

@classmethod actions

    @classmethod
    def actions(cls, sequence: List[Union[List[str], str]], *args, **kwargs):
        """ convenience method for creating a sequence of actions.

        Examples:
            >>> cls = MyCustomAction  # <-- just from local. would be generic...
            >>> action_instances = cls.actions(['echo hi world', 'echo hi cosmos'])
            >>> assert all([isinstance(a, cls) for a in action_instances])
            >>> # noinspection PyProtectedMember
            ... [o.action for o in action_instances]
            ['echo hi world', 'echo hi cosmos']
            >>> action_instances == [cls('echo hi world'), cls('echo hi cosmos')]
            True
        """
        return [cls(action=x, *args, **kwargs) for x in sequence]

__eq__

Seems like a nice-to-have to be able to compare actions, for debugging or learning purposes. I have this locally (unsure if adequate)

    def __eq__(self, other):
        if any(x is not None for x in [self.out, self.err, self.result,]):
            logging.warning('actions that have already run are never considered equal')
            return False
        if isinstance(other, self.__class__):
            comparison = dict(
                _action=self._action == other._action,
                task=self.task == other.task,
                values=self.values==other.values,
                save_out=self.save_out ==other.save_out,
                shell=self.shell==other.shell,
                encoding=self.encoding == other.encoding,
                decode_error=self.decode_error == other.decode_error,
                pkwargs=self.pkwargs == other.pkwargs,
                buffering=self.buffering == other.buffering,
            )
            eq = all(comparison.values())
            pformat(comparison, indent=2)
            logging.debug(
                f'{self!r}=={other!r} => {eq} -- comparison detail: {comparison}')
            return eq
        return False
floer32 commented 5 years ago

I think a few such things would continue PyDoit's (awesome) approach of getting things to be concise and clear, while staying very Pythonic. In this case I have a Host class subclassing tools.Interactive, and it's very clear to have {'actions': Host.actions(['1', '2', ...])}. The __eq__ was just something I used while testing (notice it is in the doctest for actions() classmethod).

For a bit more context ... I also have a subclass of Host called DockerComposeExec, and {'actions': DockerComposeExec.actions(['1', '2', ...])} is very clear as well... (Though in our case we alias for brevity, dcdo = DockerComposeExec.actions; so we have this concise little thing, {'actions': dcdo(['pytest', 'another-command', 'etc']), yet in code there's no magic, all Pythonic in the doit way.

This issue isn't about those Action subclasses, of course, just trying to paint a picture of where some utility methods might be helpful.

schettino72 commented 5 years ago

doit was designed so that people can build utility functions like this ... but I like keep doit core without those, unless this utility are popular and non-trivial to implement.

ps. doit doesnt support python2 anymore :grimacing: