diff --git a/docs/entities.rst b/docs/entities.rst index 5e9d17e..b55c02c 100644 --- a/docs/entities.rst +++ b/docs/entities.rst @@ -22,6 +22,9 @@ This document brings information about all entities that are used by |travispy| .. autoclass:: Entity :no-show-inheritance: +.. module:: travispy.entities._stateful +.. autoclass:: Stateful + .. module:: travispy.entities._restartable .. autoclass:: Restartable diff --git a/travispy/_tests/test_stateful.py b/travispy/_tests/test_stateful.py new file mode 100644 index 0000000..841df73 --- /dev/null +++ b/travispy/_tests/test_stateful.py @@ -0,0 +1,53 @@ +from travispy.entities._stateful import Stateful +import pytest + + +#=================================================================================================== +# Test +#=================================================================================================== +class Test: + + @pytest.mark.parametrize( + 'state, color, expected_true_properties', [ + (Stateful.CREATED, Stateful.YELLOW, (Stateful.CREATED, Stateful.YELLOW, 'pending')), + (Stateful.QUEUED, Stateful.YELLOW, (Stateful.CREATED, Stateful.QUEUED, Stateful.YELLOW, 'pending')), + (Stateful.STARTED, Stateful.YELLOW, (Stateful.CREATED, Stateful.QUEUED, Stateful.STARTED, Stateful.YELLOW, 'pending', 'running')), + (Stateful.PASSED, Stateful.GREEN, (Stateful.CREATED, Stateful.QUEUED, Stateful.STARTED, Stateful.PASSED, Stateful.GREEN, 'finished', 'successful')), + (Stateful.FAILED, Stateful.RED, (Stateful.CREATED, Stateful.QUEUED, Stateful.STARTED, Stateful.FAILED, Stateful.RED, 'finished', 'unsuccessful')), + (Stateful.ERRORED, Stateful.RED, (Stateful.CREATED, Stateful.QUEUED, Stateful.STARTED, Stateful.ERRORED, Stateful.RED, 'finished', 'unsuccessful')), + (Stateful.CANCELED, Stateful.RED, (Stateful.CREATED, Stateful.QUEUED, Stateful.STARTED, Stateful.CANCELED, Stateful.RED, 'finished', 'unsuccessful')), + (Stateful.READY, Stateful.GREEN, (Stateful.CREATED, Stateful.QUEUED, Stateful.STARTED, Stateful.READY, Stateful.GREEN, 'finished')), + ]) + def test_stateful(self, state, color, expected_true_properties): + stateful = Stateful(None) + + assert hasattr(stateful, 'state') == False + with pytest.raises(AttributeError): + stateful.created + + stateful.state = None + with pytest.raises(ValueError): + stateful.created + + stateful.state = state + assert stateful.color == color + + for prop in [ + Stateful.CREATED, + Stateful.QUEUED, + Stateful.STARTED, + Stateful.PASSED, + Stateful.FAILED, + Stateful.ERRORED, + Stateful.CANCELED, + Stateful.READY, + Stateful.GREEN, + Stateful.YELLOW, + Stateful.RED, + 'pending', + 'running', + 'finished', + 'successful', + 'unsuccessful', + ]: + assert getattr(stateful, prop) == (prop in expected_true_properties) diff --git a/travispy/_tests/test_travispy.py b/travispy/_tests/test_travispy.py index c6ef1d5..ea0d40c 100644 --- a/travispy/_tests/test_travispy.py +++ b/travispy/_tests/test_travispy.py @@ -166,6 +166,7 @@ def test_repos(self, repo_slug): assert hasattr(repo, 'last_build_duration') assert hasattr(repo, 'last_build_started_at') assert hasattr(repo, 'last_build_finished_at') + assert repo.state == repo.last_build_state def test_user(self): diff --git a/travispy/entities/_restartable.py b/travispy/entities/_restartable.py index f858057..0633d74 100644 --- a/travispy/entities/_restartable.py +++ b/travispy/entities/_restartable.py @@ -1,11 +1,11 @@ -from ._entity import Entity +from ._stateful import Stateful #=================================================================================================== # Restartable #=================================================================================================== -class Restartable(Entity): +class Restartable(Stateful): ''' Base class for restartable entities such as :class:`.Build` and :class:`.Job`. ''' diff --git a/travispy/entities/_stateful.py b/travispy/entities/_stateful.py new file mode 100644 index 0000000..d04eaf2 --- /dev/null +++ b/travispy/entities/_stateful.py @@ -0,0 +1,313 @@ +from ._entity import Entity + + + +#=================================================================================================== +# Stateful +#=================================================================================================== +class Stateful(Entity): + ''' + Base class for stateful entities such as :class:`.Repo`, :class:`.Build` and :class:`.Job`. + + .. attribute:: CANCELED + + Constant representing state ``canceled``. Should not be changed. + + .. attribute:: CREATED + + Constant representing state ``created``. Should not be changed. + + .. attribute:: QUEUED + + Constant representing state ``queued``. Should not be changed. + + .. attribute:: STARTED + + Constant representing state ``started``. Should not be changed. + + .. attribute:: PASSED + + Constant representing state ``passed``. Should not be changed. + + .. attribute:: FAILED + + Constant representing state ``failed``. Should not be changed. + + .. attribute:: ERRORED + + Constant representing state ``errored``. Should not be changed. + + .. attribute:: READY + + Constant representing state ``ready``. Should not be changed. + + .. attribute:: GREEN + + Constant representing state color ``green``. Should not be changed. + + .. attribute:: YELLOW + + Constant representing state color ``yellow``. Should not be changed. + + .. attribute:: RED + + Constant representing state color ``red``. Should not be changed. + + :ivar str state: + + Current state. Possible values are: + + - :attr:`.CANCELED` + - :attr:`.CREATED` + - :attr:`.QUEUED` + - :attr:`.STARTED` + - :attr:`.PASSED` + - :attr:`.FAILED` + - :attr:`.ERRORED` + - :attr:`.READY` + ''' + + __slots__ = ['state'] + + # States --------------------------------------------------------------------------------------- + CANCELED = 'canceled' + CREATED = 'created' + ERRORED = 'errored' + FAILED = 'failed' + PASSED = 'passed' + QUEUED = 'queued' + READY = 'ready' + STARTED = 'started' + + # Colors --------------------------------------------------------------------------------------- + GREEN = 'green' + YELLOW = 'yellow' + RED = 'red' + + @property + def created(self): + ''' + :rtype: bool + :returns: + ``True`` if entity build process was created successfully. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() + return hasattr(self, 'state') + + + @property + def queued(self): + ''' + :rtype: bool + :returns: + ``True`` if entity was already queued sometime in the build process. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() + return self.state != self.CREATED + + + @property + def started(self): + ''' + :rtype: bool + :returns: + ``True`` if entity was already started sometime in the build process. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() + return self.state not in [self.CREATED, self.QUEUED] + + + @property + def passed(self): + ''' + :rtype: bool + :returns: + ``True`` if build process was finished successfully. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() + return self.state == self.PASSED + + + @property + def failed(self): + ''' + :rtype: bool + :returns: + ``True`` if build process failed. This is usually related to failures on tests. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() + return self.state == self.FAILED + + + @property + def errored(self): + ''' + :rtype: bool + :returns: + ``True`` if build process got errors. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() + return self.state == self.ERRORED + + + @property + def canceled(self): + ''' + :rtype: bool + :returns: + ``True`` if build process was canceled. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() + return self.state == self.CANCELED + + + @property + def ready(self): + ''' + :rtype: bool + :returns: + ``True`` if build process is ready. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() + return self.state == self.READY + + + @property + def pending(self): + ''' + :rtype: bool + :returns: + ``True`` if build was scheduled but was not finished. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() + return self.state in [self.CREATED, self.STARTED, self.QUEUED] + + + @property + def running(self): + ''' + :rtype: bool + :returns: + ``True`` if build process is running. + + .. seealso:: :meth:`.check_state` + ''' + self.check_state() + return self.state == self.STARTED + + + @property + def finished(self): + ''' + :rtype: bool + :returns: + ``True`` if build process is finished. + ''' + return not self.pending + + + @property + def successful(self): + ''' + .. seealso:: :attr:`.passed` + ''' + return self.passed + + + @property + def unsuccessful(self): + ''' + :rtype: bool + :returns: + ``True`` if build process was finished unsuccessfully. + ''' + return self.errored or self.failed or self.canceled + + + @property + def color(self): + ''' + :rtype: bool + :returns: + The color related to current build state. Possible values are: + + - :attr:`.GREEN`: when build has passed or it is ready. + - :attr:`.YELLOW`: when build process is running. + - :attr:`.RED`: when build has failed somehow. + ''' + if self.passed or self.ready: + return self.GREEN + + elif self.pending: + return self.YELLOW + + elif self.unsuccessful: + return self.RED + + + @property + def green(self): + ''' + :rtype: bool + :returns: + ``True`` if build :attr:`.color` is :attr:`.GREEN`. + ''' + return self.color == self.GREEN + + + @property + def yellow(self): + ''' + :rtype: bool + :returns: + ``True`` if build :attr:`.color` is :attr:`.YELLOW`. + ''' + return self.color == self.YELLOW + + + @property + def red(self): + ''' + :rtype: bool + :returns: + ``True`` if build :attr:`.color` is :attr:`.RED`. + ''' + return self.color == self.RED + + + def check_state(self): + ''' + Method responsible for checking and validating current :attr:`.state`. + + :raises AttributeError: when :attr:`.state` does not exist. + :raises ValueError: when :attr:`.state` value is not supported. + ''' + if self.state not in [ + self.CANCELED, + self.CREATED, + self.ERRORED, + self.FAILED, + self.PASSED, + self.QUEUED, + self.READY, + self.STARTED, + ]: + raise ValueError('unknown state %s for %s' % (self.state, self.__class__.__name__)) diff --git a/travispy/entities/build.py b/travispy/entities/build.py index 110fd1b..2dad8c9 100644 --- a/travispy/entities/build.py +++ b/travispy/entities/build.py @@ -28,9 +28,6 @@ class Build(Restartable): :ivar dict config: Build config (secure values and ssh key removed). It comes from ``.travis.yml`` file. - :ivar str state: - Build state. - :ivar str started_at: Time the build was started. @@ -53,7 +50,6 @@ class Build(Restartable): 'pull_request_title', 'pull_request_number', 'config', - 'state', 'started_at', 'finished_at', 'duration', diff --git a/travispy/entities/job.py b/travispy/entities/job.py index a7ecb26..2698934 100644 --- a/travispy/entities/job.py +++ b/travispy/entities/job.py @@ -25,9 +25,6 @@ class Job(Restartable): :ivar dict config: Job config (secure values and ssh key removed). It comes from ``.travis.yml`` file. - :ivar str state: - Job state. - :ivar str started_at: Time the job was started. @@ -55,7 +52,6 @@ class Job(Restartable): 'log_id', 'number', 'config', - 'state', 'started_at', 'finished_at', 'duration', diff --git a/travispy/entities/repo.py b/travispy/entities/repo.py index 81dcf7d..249259e 100644 --- a/travispy/entities/repo.py +++ b/travispy/entities/repo.py @@ -1,11 +1,11 @@ -from ._entity import Entity +from ._stateful import Stateful #=================================================================================================== # Repo #=================================================================================================== -class Repo(Entity): +class Repo(Stateful): ''' :ivar str slug: Repository slug. @@ -51,3 +51,12 @@ class Repo(Entity): 'github_language', 'active', ] + + @property + def state(self): + ''' + :class:`.Repo` state is given through ``last_build_state``. + + .. seealso:: :class:`.Stateful` for ``state`` full documentation. + ''' + return self.last_build_state