Skip to content

Commit

Permalink
More documentation updates/changes split reference into user and inte…
Browse files Browse the repository at this point in the history
…rnal sections
  • Loading branch information
darjus committed Jun 8, 2016
1 parent 19524f1 commit 1ece196
Show file tree
Hide file tree
Showing 20 changed files with 205 additions and 135 deletions.
90 changes: 73 additions & 17 deletions botoflow/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,86 @@
# express or implied. See the License for the specific language governing
# permissions and limitations under the License.

"""This module contains various constants
"""This module contains various workflow, activity and time constants.
:data USE_WORKER_TASK_LIST: Use task list of the ActivityWorker or
WorkflowWorker that is used to register activity or workflow as the defalt
Tasklist settings
+++++++++++++++++
.. py:data:: USE_WORKER_TASK_LIST
Use task list of the ActivityWorker or WorkflowWorker that is used to register activity or workflow as the default
task list for the activity or workflow type.
:data NO_DEFAULT_TASK_LIST: Do not specify task list on registration. Which
means that task list is required when scheduling activity.
.. py:data:: NO_DEFAULT_TASK_LIST
Do not specify task list on registration. Which means that task list is required when scheduling activity.
Child workflow termination policy settings
++++++++++++++++++++++++++++++++++++++++++
You can learn more about *Child Workflows* from the official
`SWF Developer Guide <http://docs.aws.amazon.com/amazonswf/latest/developerguide/swf-dg-adv.html#swf-dev-adv-child-workflows>`_.
.. py:data:: CHILD_TERMINATE
The child executions will be terminated.
.. py:data:: CHILD_REQUEST_CANCEL
Request to cancel will be attempted for each child execution by recording a
:py:class:`~botoflow.history_events.events.WorkflowExecutionCancelRequested` event in its history. It is up to the
decider to take appropriate actions when it receives an execution history with this event.
.. py:data:: CHILD_ABANDON
Child policy to abandon the parent workflow. If there are any child workflows still running the will be allowed
to continue without notice.
Time multipliers
++++++++++++++++
The time multiplier constants are just an attempt at making setting various workflow or activity timeouts more readable.
Consider the following examples and their readability:
.. code-block:: python
@activities(schedule_to_start_timeout=120,
start_to_close_timeout=23400)
class ImportantBusinessActivities(object): ...
# using the time multiplier constants
from botoflow.constants import MINUTES, HOURS
@activities(schedule_to_start_timeout=2*MINUTES,
start_to_close_timeout=30*MINUTES + 6*HOURS)
class ImportantBusinessActivities(object): ...
.. py:data:: SECONDS
``2*SECONDS = 2``
.. py:data:: MINUTES
``2*MINUTES = 120``
.. py:data:: HOURS
``2*HOURS = 7200``
.. py:data:: DAYS
:data CHILD_TERMINATE: The child executions will be terminated.
``2*DAYS = 172800``
:data CHILD_REQUEST_CANCEL: Request to cancel will be attempted for each child
execution by recording a 'WorkflowExecutionCancelRequested' event in its
history. It is up to the decider to take appropriate actions when it
receives an execution history with this event.
:data CHILD_ABANDON: Child policy to abandon (let the child workflow continue)
the parent workflow
.. py:data:: WEEKS
:data SECONDS: 2*SECONDS = 2
:data MINUTES: 2*MINUTES = 120
:data HOURS: 2*HOURS = 7200
:data DAYS: 2*DAYS = 172800
:data WEEKS: 2*WEEKS = 1209600
``2*WEEKS = 1209600``
"""

Expand Down
16 changes: 11 additions & 5 deletions botoflow/context/activity_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@


class ActivityContext(ContextBase):
# TODO document
"""ActivityContext is accessible from within activities via
:py:func:`botoflow.get_context` and provides the ability to
retrieve information about the workflow as well as access to the
:py:meth:`heartbeat` for heartbeating the execution of activity
"""

def __init__(self, worker, task):
"""
:param worker:
:type worker: awsflow.workers.activity_worker.ActivityWorker
:type worker: botoflow.workers.activity_worker.ActivityWorker
:param task:
:type task: awsflow.workers.activity_task.ActivityTask
:return:
:type task: botoflow.workers.activity_task.ActivityTask
"""

self.worker = worker
Expand All @@ -42,12 +45,15 @@ def heartbeat(self, details=None):
:raises CancellationError: if uncaught, will record this activity as cancelled
in SWF history, and bubble up to the decider, where it will cancel the
workflow.
:return:
"""
result = self.worker.request_heartbeat(self.task, details)
if result['cancelRequested']:
raise CancellationError('Cancel was requested during activity heartbeat')

@property
def workflow_execution(self):
"""
:returns: the information about current workflow that the activity is handling
:rtype: botoflow.workflow_execution.WorkflowExecution
"""
return self.task.workflow_execution
6 changes: 5 additions & 1 deletion botoflow/context/decision_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
from .context_base import ContextBase
from ..workflow_execution import WorkflowExecution


class DecisionContext(ContextBase):
"""Decision context provides information about current workflow execution
"""

def __init__(self, decider):
self.decider = decider
Expand All @@ -41,7 +44,8 @@ def _replaying(self, value):

@property
def workflow_execution(self):
"""Returns the current workflow execution information
"""
:returns: the current workflow execution information
:rtype: botoflow.workflow_execution.WorkflowExecution
"""
return self.__workflow_execution
Expand Down
14 changes: 12 additions & 2 deletions botoflow/context/start_workflow_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,18 @@


class StartWorkflowContext(ContextBase):
"""Context provided when running within :py:class:`botoflow.workflow_starter.WorkflowStarter`.
def __init__(self, worker):
self.worker = worker
This gives you an opportunity to get access to the worker and workflow_execution. Generally though it's used
for storing internal states.
.. py:attribute:: workflow_starter
:rtype: botoflow.workflow_starter.WorkflowStarter
"""

def __init__(self, workflow_starter):
self.workflow_starter = workflow_starter

self._workflow_options_overrides = dict()
96 changes: 59 additions & 37 deletions botoflow/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ def workflow(name):
"""Using this decorator you can override the workflow name (class name) to
use.
This cam be very useful, when you're writing a newer version of the same
workflow, but you want to write it in a new class.
This can be very useful when you're writing a newer version of the same
workflow, but you want to write it in a different class.
.. code-block:: python
Expand All @@ -61,12 +61,12 @@ class ExampleWorkflowV2(ExampleWorkflow):
def example_start_v2(self, a, b, c):
pass
Now you have two classes that handle *ExampleWorkflow*, but with version
1.0 and version 2.0, which you can start by caling corresponding
example_start() and example_start_v2()
In the example above, you have two classes that handle *ExampleWorkflow*, but with version
1.0 and version 2.0, which you can start by calling the corresponding
example_start() and example_start_v2() class methods.
:param str name: Specifies the name of the workflow type. If not set, the
name will be defaulted to the workflow definition class name.
default is to use the workflow definition class name.
"""
def _workflow(cls):
Expand All @@ -84,10 +84,11 @@ def execute(version,
data_converter=None,
description=None,
skip_registration=False):
"""Use this decorator indicates the entry point of the workflow.
"""This decorator indicates the entry point of the workflow.
The entry point of the workflow can be invoked remotely by your application
using clients that are generated automatically by the botoflow.
using :py:class`~botoflow.workflow_starter.WorkflowStarter` or direct API call from ``boto3`` or
`AWS CLI <http://docs.aws.amazon.com/cli/latest/reference/swf/start-workflow-execution.html>`_.
:param str version: Required version of the workflow type. Maximum length
is 64 characters.
Expand Down Expand Up @@ -302,85 +303,106 @@ def retry_activity(stop_max_attempt_number=None, stop_max_delay=None, wait_fixed
wait_random_max=None, wait_incrementing_start=None, wait_incrementing_increment=None,
wait_exponential_multiplier=None, wait_exponential_max=None, retry_on_exception=None,
retry_on_result=None, wrap_exception=False, stop_func=None, wait_func=None):
"""Retry activity decorator
"""Decorator to indicate retrying policy for the activity
(based on the :py:mod:`retrying` library)
Based on the `retrying <https://pypi.python.org/pypi/retrying>`_ library, but uses **seconds instead of
milliseconds** for waiting as SWF does not support subsecond granularity as well as to make it consistent with the
rest of botoflow.
**Examples:**
.. code-block:: python
:caption: Retry forever
:caption: Retry forever:
@retry
@retry_activity
@activity(version='1.0', start_to_close_timeout=float('inf'))
def never_give_up_never_surrender_activity():
print "Retry forever ignoring Exceptions, don't wait between retries"
print("Retry forever ignoring Exceptions, don't "
"wait between retries")
.. code-block:: python
:caption: Order of the activity decorators does not matter:
@activity(version='1.0', start_to_close_timeout=float('inf'))
@retry_activity
def never_give_up_never_surrender_activity():
print("Retry forever ignoring Exceptions, don't "
"wait between retries and don't care about order")
.. code-block:: python
:caption: Retry only a certain number of times
:caption: Retry only a certain number of times:
@retry(stop_max_attempt_number=7)
@retry_activity(stop_max_attempt_number=7)
@activity(version='1.0', start_to_close_timeout=10*MINUTES)
def stop_after_7_attempts_activity():
print "Stopping after 7 attempts"
print("Stopping after 7 attempts")
.. code-block:: python
:caption: Stop retrying after 10 seconds
@retry(stop_max_delay=10*SECONDS)
@retry_activity(stop_max_delay=10*SECONDS)
@activity(version='1.0', start_to_close_timeout=10*MINUTES)
def stop_after_10_s_activity():
print "Stopping after 10 seconds"
print("Stopping after 10 seconds")
.. code-block:: python
:caption: Wait a random amount of time
:caption: Wait a random amount of time:
@retry(wait_random_min=1*SECONDS, wait_random_max=2*SECONDS)
@retry_activity(wait_random_min=1*SECONDS,
wait_random_max=2*SECONDS)
@activity(version='1.0', start_to_close_timeout=10*MINUTES)
def wait_random_1_to_2_s_activity():
print "Randomly wait 1 to 2 seconds between retries"
print("Randomly wait 1 to 2 seconds between retries")
.. code-block:: python
:caption: Wait an exponentially growing amount of time
:caption: Wait an exponentially growing amount of time:
@retry(wait_exponential_multiplier=1*SECONDS, wait_exponential_max=10*SECONDS)
@retry_activity(wait_exponential_multiplier=1*SECONDS,
wait_exponential_max=10*SECONDS)
@activity(version='1.0', start_to_close_timeout=10*MINUTES)
def wait_exponential_1_activity():
print "Wait 2^x * 1 second between each retry, up to 10 seconds, then 10 seconds afterwards"
print("Wait 2^x * 1 second between each retry, up to 10 "
"seconds, then 10 seconds afterwards")
.. code-block:: python
:caption: Retry on exception
:caption: Retry on exception:
@retry(retry_on_exception=retry_on_exception(IOError, OSError))
@retry_activity(retry_on_exception=retry_on_exception(IOError, OSError))
@activity(version='1.0', start_to_close_timeout=10*MINUTES)
def might_io_os_error():
print "Retry forever with no wait if an IOError or OSError occurs, raise any other errors"
print("Retry forever with no wait if an IOError or OSError "
"occurs, raise any other errors")
.. code-block:: python
:caption: Custom exception retryer
:caption: Custom exception retryer:
def retry_if_io_error(exception):
\"\"\"Return True if we should retry (in this case when it's an IOError), False otherwise\"\"\"
if isinstance(exception, ActivityTaskFailedError):
return isinstance(exception.cause, IOError)
\"\"\"Return True if we should retry (in this case when it's an IOError),
False otherwise
\"\"\"
if isinstance(exception, ActivityTaskFailedError):
return isinstance(exception.cause, IOError)
@retry(retry_on_exception=retry_if_io_error)
@retry_activity(retry_on_exception=retry_if_io_error)
@activity(version='1.0', start_to_close_timeout=10*MINUTES)
def might_io_error():
print "Retry forever with no wait if an IOError occurs, raise any other errors"
print("Retry forever with no wait if an IOError occurs, "
"raise any other errors")
.. code-block:: python
:caption: Custom retryer based on result
:caption: Custom retryer based on result:
def retry_if_result_none(result):
\"\"\"Return True if we should retry (in this case when result is None), False otherwise\"\"\"
\"\"\"Return True if we should retry (in this case when result is None),
False otherwise
\"\"\"
return result is None
@retry(retry_on_result=retry_if_result_none)
@retry_activity(retry_on_result=retry_if_result_none)
@activity(version='1.0', start_to_close_timeout=10*MINUTES)
def might_return_none():
print "Retry forever ignoring Exceptions with no wait if return value is None"
print("Retry forever ignoring Exceptions with no wait if "
"return value is None")
:param stop_max_attempt_number: Stop retrying after reaching this attempt number. Default is 3 attempts.
:type stop_max_attempt_number: int
Expand Down
19 changes: 18 additions & 1 deletion botoflow/workflow_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,24 @@

from collections import namedtuple

WorkflowExecution = namedtuple('WorkflowExecution', 'workflow_id run_id')

class WorkflowExecution(namedtuple('WorkflowExecution', 'workflow_id run_id')):
"""Contains workflow execution information provided by SWF.
.. py:attribute:: workflow_id
Either provided or randomly generated Workflow ID. There can only be one workflow running with the same
Workflow ID.
:rtype: str
.. py:attribute:: run_id
SWF generated and provided Run ID associated with a particular workflow execution
:rtype: str
"""


def workflow_execution_from_swf_event(event):
Expand Down
Loading

0 comments on commit 1ece196

Please sign in to comment.