€cdocutils.nodes document q)q}q(U nametypesq}q(Xwhat is a workflow?qNX-how does amazon swf help you accomplish this?qNXmiscellaneous blog articlesqˆXstackoverflow questionsq ˆXsimpleworkflowq ˆX prerequisitesq NXdecision tasksq NX sub-workflowsq NXfiltering eventsqNXmiscqNX paginationqNXwhy use workflows?qNXserial activity executionqNXamazon swf api referenceqˆX helloworldqNXamazon simple workflow tutorialqNXparallel activity executionqNuUsubstitution_defsq}qUparse_messagesq]qUcurrent_sourceqNU decorationqNUautofootnote_startqKUnameidsq}q(hUwhat-is-a-workflowq hU,how-does-amazon-swf-help-you-accomplish-thisq!hUmiscellaneous-blog-articlesq"h Ustackoverflow-questionsq#h Usimpleworkflowq$h U prerequisitesq%h Udecision-tasksq&h U sub-workflowsq'hUfiltering-eventsq(hUmiscq)hU paginationq*hUwhy-use-workflowsq+hUserial-activity-executionq,hUamazon-swf-api-referenceq-hU helloworldq.hUamazon-simple-workflow-tutorialq/hUparallel-activity-executionq0uUchildrenq1]q2(cdocutils.nodes comment q3)q4}q5(U rawsourceq6X_swf_tut: :Authors: Slawek "oozie" Ligus , Brad Morris Uparentq7hUsourceq8X=/Users/kyleknap/Documents/GitHub/boto/docs/source/swf_tut.rstq9Utagnameq:Ucommentq;U attributesq<}q=(U xml:spaceq>Upreserveq?Uidsq@]UbackrefsqA]UdupnamesqB]UclassesqC]UnamesqD]uUlineqEKUdocumentqFhh1]qGcdocutils.nodes Text qHX_swf_tut: :Authors: Slawek "oozie" Ligus , Brad Morris qI…qJ}qK(h6Uh7h4ubaubcdocutils.nodes section qL)qM}qN(h6Uh7hh8h9h:UsectionqOh<}qP(hB]hC]hA]h@]qQh/ahD]qRhauhEKhFhh1]qS(cdocutils.nodes title qT)qU}qV(h6XAmazon Simple Workflow TutorialqWh7hMh8h9h:UtitleqXh<}qY(hB]hC]hA]h@]hD]uhEKhFhh1]qZhHXAmazon Simple Workflow Tutorialq[…q\}q](h6hWh7hUubaubcdocutils.nodes paragraph q^)q_}q`(h6XHThis tutorial focuses on boto's interface to AWS SimpleWorkflow service.qah7hMh8h9h:U paragraphqbh<}qc(hB]hC]hA]h@]hD]uhEKhFhh1]qdhHXHThis tutorial focuses on boto's interface to AWS SimpleWorkflow service.qe…qf}qg(h6hah7h_ubaubcdocutils.nodes target qh)qi}qj(h6X... _SimpleWorkflow: http://aws.amazon.com/swf/h7hMh8h9h:Utargetqkh<}ql(UrefuriqmXhttp://aws.amazon.com/swf/h@]qnh$ahA]hB]hC]hD]qoh auhEK hFhh1]ubhL)qp}qq(h6Uh7hMh8h9h:hOh<}qr(hB]hC]hA]h@]qsh ahD]qthauhEK hFhh1]qu(hT)qv}qw(h6XWhat is a workflow?qxh7hph8h9h:hXh<}qy(hB]hC]hA]h@]hD]uhEK hFhh1]qzhHXWhat is a workflow?q{…q|}q}(h6hxh7hvubaubh^)q~}q(h6X&A workflow is a sequence of multiple activities aimed at accomplishing a well-defined objective. For instance, booking an airline ticket as a workflow may encompass multiple activities, such as selection of itinerary, submission of personal details, payment validation and booking confirmation.q€h7hph8h9h:hbh<}q(hB]hC]hA]h@]hD]uhEKhFhh1]q‚hHX&A workflow is a sequence of multiple activities aimed at accomplishing a well-defined objective. For instance, booking an airline ticket as a workflow may encompass multiple activities, such as selection of itinerary, submission of personal details, payment validation and booking confirmation.qƒ…q„}q…(h6h€h7h~ubaubcdocutils.nodes definition_list q†)q‡}qˆ(h6Uh7hph8Nh:Udefinition_listq‰h<}qŠ(hB]hC]hA]h@]hD]uhENhFhh1]q‹cdocutils.nodes definition_list_item qŒ)q}qŽ(h6X¡Except for the start and completion of a workflow, each step has a well-defined predecessor and successor. With that - on successful completion of an activity the workflow can progress with its execution, - when one of workflow's activities fails it can be retried, - and when it keeps failing repeatedly the workflow may regress to the previous step to gather alternative inputs or it may simply fail at that stage. h7h‡h8h9h:Udefinition_list_itemqh<}q(hB]hC]hA]h@]hD]uhEKh1]q‘(cdocutils.nodes term q’)q“}q”(h6XtExcept for the start and completion of a workflow, each step has a well-defined predecessor and successor. With thatq•h7hh8h9h:Utermq–h<}q—(hB]hC]hA]h@]hD]uhEKh1]q˜hHXtExcept for the start and completion of a workflow, each step has a well-defined predecessor and successor. With thatq™…qš}q›(h6h•h7h“ubaubcdocutils.nodes definition qœ)q}qž(h6Uh<}qŸ(hB]hC]hA]h@]hD]uh7hh1]q cdocutils.nodes bullet_list q¡)q¢}q£(h6Uh<}q¤(Ubulletq¥X-h@]hA]hB]hC]hD]uh7hh1]q¦(cdocutils.nodes list_item q§)q¨}q©(h6XUon successful completion of an activity the workflow can progress with its execution,qªh<}q«(hB]hC]hA]h@]hD]uh7h¢h1]q¬h^)q­}q®(h6hªh7h¨h8h9h:hbh<}q¯(hB]hC]hA]h@]hD]uhEKh1]q°hHXUon successful completion of an activity the workflow can progress with its execution,q±…q²}q³(h6hªh7h­ubaubah:U list_itemq´ubh§)qµ}q¶(h6X:when one of workflow's activities fails it can be retried,q·h<}q¸(hB]hC]hA]h@]hD]uh7h¢h1]q¹h^)qº}q»(h6h·h7hµh8h9h:hbh<}q¼(hB]hC]hA]h@]hD]uhEKh1]q½hHX:when one of workflow's activities fails it can be retried,q¾…q¿}qÀ(h6h·h7hºubaubah:h´ubh§)qÁ}qÂ(h6X•and when it keeps failing repeatedly the workflow may regress to the previous step to gather alternative inputs or it may simply fail at that stage. h<}qÃ(hB]hC]hA]h@]hD]uh7h¢h1]qÄh^)qÅ}qÆ(h6X”and when it keeps failing repeatedly the workflow may regress to the previous step to gather alternative inputs or it may simply fail at that stage.qÇh7hÁh8h9h:hbh<}qÈ(hB]hC]hA]h@]hD]uhEKh1]qÉhHX”and when it keeps failing repeatedly the workflow may regress to the previous step to gather alternative inputs or it may simply fail at that stage.qÊ…qË}qÌ(h6hÇh7hÅubaubah:h´ubeh:U bullet_listqÍubah:U definitionqÎubeubaubeubhL)qÏ}qÐ(h6Uh7hMh8h9h:hOh<}qÑ(hB]hC]hA]h@]qÒh+ahD]qÓhauhEKhFhh1]qÔ(hT)qÕ}qÖ(h6XWhy use workflows?q×h7hÏh8h9h:hXh<}qØ(hB]hC]hA]h@]hD]uhEKhFhh1]qÙhHXWhy use workflows?qÚ…qÛ}qÜ(h6h×h7hÕubaubh^)qÝ}qÞ(h6XþModelling an application on a workflow provides a useful abstraction layer for writing highly-reliable programs for distributed systems, as individual responsibilities can be delegated to a set of redundant, independent and non-critical processing units.qßh7hÏh8h9h:hbh<}qà(hB]hC]hA]h@]hD]uhEKhFhh1]qáhHXþModelling an application on a workflow provides a useful abstraction layer for writing highly-reliable programs for distributed systems, as individual responsibilities can be delegated to a set of redundant, independent and non-critical processing units.qâ…qã}qä(h6hßh7hÝubaubeubhL)qå}qæ(h6Uh7hMh8h9h:hOh<}qç(hB]hC]hA]h@]qèh!ahD]qéhauhEKhFhh1]qê(hT)që}qì(h6X-How does Amazon SWF help you accomplish this?qíh7håh8h9h:hXh<}qî(hB]hC]hA]h@]hD]uhEKhFhh1]qïhHX-How does Amazon SWF help you accomplish this?qð…qñ}qò(h6híh7hëubaubh^)qó}qô(h6X…Amazon SimpleWorkflow service defines an interface for workflow orchestration and provides state persistence for workflow executions.qõh7håh8h9h:hbh<}qö(hB]hC]hA]h@]hD]uhEKhFhh1]q÷hHX…Amazon SimpleWorkflow service defines an interface for workflow orchestration and provides state persistence for workflow executions.qø…qù}qú(h6hõh7hóubaubh†)qû}qü(h6Uh7håh8Nh:h‰h<}qý(hB]hC]hA]h@]hD]uhENhFhh1]qþhŒ)qÿ}r(h6XuAmazon SWF applications involve communication between the following entities: - The Amazon Simple Workflow Service - providing centralized orchestration and workflow state persistence, - Workflow Executors - some entity starting workflow executions, typically through an action taken by a user or from a cronjob. - Deciders - a program codifying the business logic, i.e. a set of instructions and decisions. Deciders take decisions based on initial set of conditions and outcomes from activities. - Activity Workers - their objective is very straightforward: to take inputs, execute the tasks and return a result to the Service. h7hûh8h9h:hh<}r(hB]hC]hA]h@]hD]uhEK$h1]r(h’)r}r(h6XMAmazon SWF applications involve communication between the following entities:rh7hÿh8h9h:h–h<}r(hB]hC]hA]h@]hD]uhEK$h1]rhHXMAmazon SWF applications involve communication between the following entities:r…r }r (h6jh7jubaubhœ)r }r (h6Uh<}r (hB]hC]hA]h@]hD]uh7hÿh1]rh¡)r}r(h6Uh<}r(h¥X-h@]hA]hB]hC]hD]uh7j h1]r(h§)r}r(h6XhThe Amazon Simple Workflow Service - providing centralized orchestration and workflow state persistence,rh<}r(hB]hC]hA]h@]hD]uh7jh1]rh^)r}r(h6jh7jh8h9h:hbh<}r(hB]hC]hA]h@]hD]uhEK!h1]rhHXhThe Amazon Simple Workflow Service - providing centralized orchestration and workflow state persistence,r…r}r(h6jh7jubaubah:h´ubh§)r}r (h6X}Workflow Executors - some entity starting workflow executions, typically through an action taken by a user or from a cronjob.r!h<}r"(hB]hC]hA]h@]hD]uh7jh1]r#h^)r$}r%(h6j!h7jh8h9h:hbh<}r&(hB]hC]hA]h@]hD]uhEK"h1]r'hHX}Workflow Executors - some entity starting workflow executions, typically through an action taken by a user or from a cronjob.r(…r)}r*(h6j!h7j$ubaubah:h´ubh§)r+}r,(h6XµDeciders - a program codifying the business logic, i.e. a set of instructions and decisions. Deciders take decisions based on initial set of conditions and outcomes from activities.r-h<}r.(hB]hC]hA]h@]hD]uh7jh1]r/h^)r0}r1(h6j-h7j+h8h9h:hbh<}r2(hB]hC]hA]h@]hD]uhEK#h1]r3hHXµDeciders - a program codifying the business logic, i.e. a set of instructions and decisions. Deciders take decisions based on initial set of conditions and outcomes from activities.r4…r5}r6(h6j-h7j0ubaubah:h´ubh§)r7}r8(h6X‚Activity Workers - their objective is very straightforward: to take inputs, execute the tasks and return a result to the Service. h<}r9(hB]hC]hA]h@]hD]uh7jh1]r:h^)r;}r<(h6XActivity Workers - their objective is very straightforward: to take inputs, execute the tasks and return a result to the Service.r=h7j7h8h9h:hbh<}r>(hB]hC]hA]h@]hD]uhEK$h1]r?hHXActivity Workers - their objective is very straightforward: to take inputs, execute the tasks and return a result to the Service.r@…rA}rB(h6j=h7j;ubaubah:h´ubeh:hÍubah:hÎubeubaubh^)rC}rD(h6XßThe Workflow Executor contacts SWF Service and requests instantiation of a workflow. A new workflow is created and its state is stored in the service. The next time a decider contacts SWF service to ask for a decision task, it will be informed about a new workflow execution is taking place and it will be asked to advise SWF service on what the next steps should be. The decider then instructs the service to dispatch specific tasks to activity workers. At the next activity worker poll, the task is dispatched, then executed and the results reported back to the SWF, which then passes them onto the deciders. This exchange keeps happening repeatedly until the decider is satisfied and instructs the service to complete the execution.rEh7håh8h9h:hbh<}rF(hB]hC]hA]h@]hD]uhEK&hFhh1]rGhHXßThe Workflow Executor contacts SWF Service and requests instantiation of a workflow. A new workflow is created and its state is stored in the service. The next time a decider contacts SWF service to ask for a decision task, it will be informed about a new workflow execution is taking place and it will be asked to advise SWF service on what the next steps should be. The decider then instructs the service to dispatch specific tasks to activity workers. At the next activity worker poll, the task is dispatched, then executed and the results reported back to the SWF, which then passes them onto the deciders. This exchange keeps happening repeatedly until the decider is satisfied and instructs the service to complete the execution.rH…rI}rJ(h6jEh7jCubaubeubhL)rK}rL(h6Uh7hMh8h9h:hOh<}rM(hB]hC]hA]h@]rNh%ahD]rOh auhEK*hFhh1]rP(hT)rQ}rR(h6X PrerequisitesrSh7jKh8h9h:hXh<}rT(hB]hC]hA]h@]hD]uhEK*hFhh1]rUhHX PrerequisitesrV…rW}rX(h6jSh7jQubaubh^)rY}rZ(h6X~You need a valid access and secret key. The examples below assume that you have exported them to your environment, as follows:r[h7jKh8h9h:hbh<}r\(hB]hC]hA]h@]hD]uhEK,hFhh1]r]hHX~You need a valid access and secret key. The examples below assume that you have exported them to your environment, as follows:r^…r_}r`(h6j[h7jYubaubcdocutils.nodes literal_block ra)rb}rc(h6Xebash$ export AWS_ACCESS_KEY_ID= bash$ export AWS_SECRET_ACCESS_KEY=h7jKh8h9h:U literal_blockrdh<}re(Ulinenosrf‰UlanguagergXbashh>h?h@]hA]hB]hC]hD]uhEK.hFhh1]rhhHXebash$ export AWS_ACCESS_KEY_ID= bash$ export AWS_SECRET_ACCESS_KEY=ri…rj}rk(h6Uh7jbubaubh^)rl}rm(h6XYBefore workflows and activities can be used, they have to be registered with SWF service:rnh7jKh8h9h:hbh<}ro(hB]hC]hA]h@]hD]uhEK3hFhh1]rphHXYBefore workflows and activities can be used, they have to be registered with SWF service:rq…rr}rs(h6jnh7jlubaubja)rt}ru(h6X•# register.py import boto.swf.layer2 as swf from boto.swf.exceptions import SWFTypeAlreadyExistsError, SWFDomainAlreadyExistsError DOMAIN = 'boto_tutorial' VERSION = '1.0' registerables = [] registerables.append(swf.Domain(name=DOMAIN)) for workflow_type in ('HelloWorkflow', 'SerialWorkflow', 'ParallelWorkflow', 'SubWorkflow'): registerables.append(swf.WorkflowType(domain=DOMAIN, name=workflow_type, version=VERSION, task_list='default')) for activity_type in ('HelloWorld', 'ActivityA', 'ActivityB', 'ActivityC'): registerables.append(swf.ActivityType(domain=DOMAIN, name=activity_type, version=VERSION, task_list='default')) for swf_entity in registerables: try: swf_entity.register() print swf_entity.name, 'registered successfully' except (SWFDomainAlreadyExistsError, SWFTypeAlreadyExistsError): print swf_entity.__class__.__name__, swf_entity.name, 'already exists'h7jKh8h9h:jdh<}rv(jf‰jgXpythonh>h?h@]hA]hB]hC]hD]uhEK5hFhh1]rwhHX•# register.py import boto.swf.layer2 as swf from boto.swf.exceptions import SWFTypeAlreadyExistsError, SWFDomainAlreadyExistsError DOMAIN = 'boto_tutorial' VERSION = '1.0' registerables = [] registerables.append(swf.Domain(name=DOMAIN)) for workflow_type in ('HelloWorkflow', 'SerialWorkflow', 'ParallelWorkflow', 'SubWorkflow'): registerables.append(swf.WorkflowType(domain=DOMAIN, name=workflow_type, version=VERSION, task_list='default')) for activity_type in ('HelloWorld', 'ActivityA', 'ActivityB', 'ActivityC'): registerables.append(swf.ActivityType(domain=DOMAIN, name=activity_type, version=VERSION, task_list='default')) for swf_entity in registerables: try: swf_entity.register() print swf_entity.name, 'registered successfully' except (SWFDomainAlreadyExistsError, SWFTypeAlreadyExistsError): print swf_entity.__class__.__name__, swf_entity.name, 'already exists'rx…ry}rz(h6Uh7jtubaubh^)r{}r|(h6X0Execution of the above should produce no errors.r}h7jKh8h9h:hbh<}r~(hB]hC]hA]h@]hD]uhEKMhFhh1]rhHX0Execution of the above should produce no errors.r€…r}r‚(h6j}h7j{ubaubja)rƒ}r„(h6XJbash$ python -i register.py Domain boto_tutorial already exists WorkflowType HelloWorkflow already exists SerialWorkflow registered successfully ParallelWorkflow registered successfully ActivityType HelloWorld already exists ActivityA registered successfully ActivityB registered successfully ActivityC registered successfully >>>h7jKh8h9h:jdh<}r…(jf‰jgXbashh>h?h@]hA]hB]hC]hD]uhEKOhFhh1]r†hHXJbash$ python -i register.py Domain boto_tutorial already exists WorkflowType HelloWorkflow already exists SerialWorkflow registered successfully ParallelWorkflow registered successfully ActivityType HelloWorld already exists ActivityA registered successfully ActivityB registered successfully ActivityC registered successfully >>>r‡…rˆ}r‰(h6Uh7jƒubaubeubhL)rŠ}r‹(h6Uh7hMh8h9h:hOh<}rŒ(hB]hC]hA]h@]rh.ahD]rŽhauhEK]hFhh1]r(hT)r}r‘(h6X HelloWorldr’h7jŠh8h9h:hXh<}r“(hB]hC]hA]h@]hD]uhEK]hFhh1]r”hHX HelloWorldr•…r–}r—(h6j’h7jubaubh^)r˜}r™(h6XlThis example is an implementation of a minimal Hello World workflow. Its execution should unfold as follows:ršh7jŠh8h9h:hbh<}r›(hB]hC]hA]h@]hD]uhEK_hFhh1]rœhHXlThis example is an implementation of a minimal Hello World workflow. Its execution should unfold as follows:r…rž}rŸ(h6jšh7j˜ubaubcdocutils.nodes enumerated_list r )r¡}r¢(h6Uh7jŠh8h9h:Uenumerated_listr£h<}r¤(Usuffixr¥U.h@]hA]hB]Uprefixr¦UhC]hD]Uenumtyper§Uarabicr¨uhEKahFhh1]r©(h§)rª}r«(h6X A workflow execution is started.r¬h7j¡h8h9h:h´h<}r­(hB]hC]hA]h@]hD]uhENhFhh1]r®h^)r¯}r°(h6j¬h7jªh8h9h:hbh<}r±(hB]hC]hA]h@]hD]uhEKah1]r²hHX A workflow execution is started.r³…r´}rµ(h6j¬h7j¯ubaubaubh§)r¶}r·(h6X4The SWF service schedules the initial decision task.r¸h7j¡h8h9h:h´h<}r¹(hB]hC]hA]h@]hD]uhENhFhh1]rºh^)r»}r¼(h6j¸h7j¶h8h9h:hbh<}r½(hB]hC]hA]h@]hD]uhEKbh1]r¾hHX4The SWF service schedules the initial decision task.r¿…rÀ}rÁ(h6j¸h7j»ubaubaubh§)rÂ}rÃ(h6X4A decider polls for decision tasks and receives one.rÄh7j¡h8h9h:h´h<}rÅ(hB]hC]hA]h@]hD]uhENhFhh1]rÆh^)rÇ}rÈ(h6jÄh7jÂh8h9h:hbh<}rÉ(hB]hC]hA]h@]hD]uhEKch1]rÊhHX4A decider polls for decision tasks and receives one.rË…rÌ}rÍ(h6jÄh7jÇubaubaubh§)rÎ}rÏ(h6X4The decider requests scheduling of an activity task.rÐh7j¡h8h9h:h´h<}rÑ(hB]hC]hA]h@]hD]uhENhFhh1]rÒh^)rÓ}rÔ(h6jÐh7jÎh8h9h:hbh<}rÕ(hB]hC]hA]h@]hD]uhEKdh1]rÖhHX4The decider requests scheduling of an activity task.r×…rØ}rÙ(h6jÐh7jÓubaubaubh§)rÚ}rÛ(h6X5The SWF service schedules the greeting activity task.rÜh7j¡h8h9h:h´h<}rÝ(hB]hC]hA]h@]hD]uhENhFhh1]rÞh^)rß}rà(h6jÜh7jÚh8h9h:hbh<}rá(hB]hC]hA]h@]hD]uhEKeh1]râhHX5The SWF service schedules the greeting activity task.rã…rä}rå(h6jÜh7jßubaubaubh§)ræ}rç(h6X<An activity worker polls for activity task and receives one.rèh7j¡h8h9h:h´h<}ré(hB]hC]hA]h@]hD]uhENhFhh1]rêh^)rë}rì(h6jèh7jæh8h9h:hbh<}rí(hB]hC]hA]h@]hD]uhEKfh1]rîhHX<An activity worker polls for activity task and receives one.rï…rð}rñ(h6jèh7jëubaubaubh§)rò}ró(h6X+The worker completes the greeting activity.rôh7j¡h8h9h:h´h<}rõ(hB]hC]hA]h@]hD]uhENhFhh1]röh^)r÷}rø(h6jôh7jòh8h9h:hbh<}rù(hB]hC]hA]h@]hD]uhEKgh1]rúhHX+The worker completes the greeting activity.rû…rü}rý(h6jôh7j÷ubaubaubh§)rþ}rÿ(h6XGThe SWF service schedules a decision task to inform about work outcome.rh7j¡h8h9h:h´h<}r(hB]hC]hA]h@]hD]uhENhFhh1]rh^)r}r(h6jh7jþh8h9h:hbh<}r(hB]hC]hA]h@]hD]uhEKhh1]rhHXGThe SWF service schedules a decision task to inform about work outcome.r…r}r (h6jh7jubaubaubh§)r }r (h6X3The decider polls and receives a new decision task.r h7j¡h8h9h:h´h<}r (hB]hC]hA]h@]hD]uhENhFhh1]rh^)r}r(h6j h7j h8h9h:hbh<}r(hB]hC]hA]h@]hD]uhEKih1]rhHX3The decider polls and receives a new decision task.r…r}r(h6j h7jubaubaubh§)r}r(h6X*The decider schedules workflow completion.rh7j¡h8h9h:h´h<}r(hB]hC]hA]h@]hD]uhENhFhh1]rh^)r}r(h6jh7jh8h9h:hbh<}r(hB]hC]hA]h@]hD]uhEKjh1]rhHX*The decider schedules workflow completion.r…r }r!(h6jh7jubaubaubh§)r"}r#(h6X!The workflow execution finishes. h7j¡h8h9h:h´h<}r$(hB]hC]hA]h@]hD]uhENhFhh1]r%h^)r&}r'(h6X The workflow execution finishes.r(h7j"h8h9h:hbh<}r)(hB]hC]hA]h@]hD]uhEKkh1]r*hHX The workflow execution finishes.r+…r,}r-(h6j(h7j&ubaubaubeubh^)r.}r/(h6X)Workflow logic is encoded in the decider:r0h7jŠh8h9h:hbh<}r1(hB]hC]hA]h@]hD]uhEKmhFhh1]r2hHX)Workflow logic is encoded in the decider:r3…r4}r5(h6j0h7j.ubaubja)r6}r7(h6XË# hello_decider.py import boto.swf.layer2 as swf DOMAIN = 'boto_tutorial' ACTIVITY = 'HelloWorld' VERSION = '1.0' TASKLIST = 'default' class HelloDecider(swf.Decider): domain = DOMAIN task_list = TASKLIST version = VERSION def run(self): history = self.poll() if 'events' in history: # Find workflow events not related to decision scheduling. workflow_events = [e for e in history['events'] if not e['eventType'].startswith('Decision')] last_event = workflow_events[-1] decisions = swf.Layer1Decisions() if last_event['eventType'] == 'WorkflowExecutionStarted': decisions.schedule_activity_task('saying_hi', ACTIVITY, VERSION, task_list=TASKLIST) elif last_event['eventType'] == 'ActivityTaskCompleted': decisions.complete_workflow_execution() self.complete(decisions=decisions) return Trueh7jŠh8h9h:jdh<}r8(jf‰jgXpythonh>h?h@]hA]hB]hC]hD]uhEKohFhh1]r9hHXË# hello_decider.py import boto.swf.layer2 as swf DOMAIN = 'boto_tutorial' ACTIVITY = 'HelloWorld' VERSION = '1.0' TASKLIST = 'default' class HelloDecider(swf.Decider): domain = DOMAIN task_list = TASKLIST version = VERSION def run(self): history = self.poll() if 'events' in history: # Find workflow events not related to decision scheduling. workflow_events = [e for e in history['events'] if not e['eventType'].startswith('Decision')] last_event = workflow_events[-1] decisions = swf.Layer1Decisions() if last_event['eventType'] == 'WorkflowExecutionStarted': decisions.schedule_activity_task('saying_hi', ACTIVITY, VERSION, task_list=TASKLIST) elif last_event['eventType'] == 'ActivityTaskCompleted': decisions.complete_workflow_execution() self.complete(decisions=decisions) return Truer:…r;}r<(h6Uh7j6ubaubh^)r=}r>(h6XThe activity worker is responsible for printing the greeting message when the activity task is dispatched to it by the service:r?h7jŠh8h9h:hbh<}r@(hB]hC]hA]h@]hD]uhEKhFhh1]rAhHXThe activity worker is responsible for printing the greeting message when the activity task is dispatched to it by the service:rB…rC}rD(h6j?h7j=ubaubja)rE}rF(h6X€import boto.swf.layer2 as swf DOMAIN = 'boto_tutorial' VERSION = '1.0' TASKLIST = 'default' class HelloWorker(swf.ActivityWorker): domain = DOMAIN version = VERSION task_list = TASKLIST def run(self): activity_task = self.poll() if 'activityId' in activity_task: print 'Hello, World!' self.complete() return Trueh7jŠh8h9h:jdh<}rG(jf‰jgXpythonh>h?h@]hA]hB]hC]hD]uhEK‘hFhh1]rHhHX€import boto.swf.layer2 as swf DOMAIN = 'boto_tutorial' VERSION = '1.0' TASKLIST = 'default' class HelloWorker(swf.ActivityWorker): domain = DOMAIN version = VERSION task_list = TASKLIST def run(self): activity_task = self.poll() if 'activityId' in activity_task: print 'Hello, World!' self.complete() return TruerI…rJ}rK(h6Uh7jEubaubh^)rL}rM(h6X<With actors implemented we can spin up a workflow execution:rNh7jŠh8h9h:hbh<}rO(hB]hC]hA]h@]hD]uhEK¦hFhh1]rPhHX<With actors implemented we can spin up a workflow execution:rQ…rR}rS(h6jNh7jLubaubja)rT}rU(h6X©$ python >>> import boto.swf.layer2 as swf >>> execution = swf.WorkflowType(name='HelloWorkflow', domain='boto_tutorial', version='1.0', task_list='default').start() >>>h7jŠh8h9h:jdh<}rV(jf‰jgXbashh>h?h@]hA]hB]hC]hD]uhEK¨hFhh1]rWhHX©$ python >>> import boto.swf.layer2 as swf >>> execution = swf.WorkflowType(name='HelloWorkflow', domain='boto_tutorial', version='1.0', task_list='default').start() >>>rX…rY}rZ(h6Uh7jTubaubh^)r[}r\(h6X£From separate terminals run an instance of a worker and a decider to carry out a workflow execution (the worker and decider may run from two independent machines).r]h7jŠh8h9h:hbh<}r^(hB]hC]hA]h@]hD]uhEK¯hFhh1]r_hHX£From separate terminals run an instance of a worker and a decider to carry out a workflow execution (the worker and decider may run from two independent machines).r`…ra}rb(h6j]h7j[ubaubja)rc}rd(h6XE$ python -i hello_decider.py >>> while HelloDecider().run(): pass ...h7jŠh8h9h:jdh<}re(jf‰jgXbashh>h?h@]hA]hB]hC]hD]uhEK±hFhh1]rfhHXE$ python -i hello_decider.py >>> while HelloDecider().run(): pass ...rg…rh}ri(h6Uh7jcubaubja)rj}rk(h6XQ$ python -i hello_worker.py >>> while HelloWorker().run(): pass ... Hello, World!h7jŠh8h9h:jdh<}rl(jf‰jgXbashh>h?h@]hA]hB]hC]hD]uhEK·hFhh1]rmhHXQ$ python -i hello_worker.py >>> while HelloWorker().run(): pass ... Hello, World!rn…ro}rp(h6Uh7jjubaubh^)rq}rr(h6XƒGreat. Now, to see what just happened, go back to the original terminal from which the execution was started, and read its history.rsh7jŠh8h9h:hbh<}rt(hB]hC]hA]h@]hD]uhEK¾hFhh1]ruhHXƒGreat. Now, to see what just happened, go back to the original terminal from which the execution was started, and read its history.rv…rw}rx(h6jsh7jqubaubja)ry}rz(h6Xß >>> execution.history() [{'eventId': 1, 'eventTimestamp': 1381095173.2539999, 'eventType': 'WorkflowExecutionStarted', 'workflowExecutionStartedEventAttributes': {'childPolicy': 'TERMINATE', 'executionStartToCloseTimeout': '3600', 'parentInitiatedEventId': 0, 'taskList': {'name': 'default'}, 'taskStartToCloseTimeout': '300', 'workflowType': {'name': 'HelloWorkflow', 'version': '1.0'}}}, {'decisionTaskScheduledEventAttributes': {'startToCloseTimeout': '300', 'taskList': {'name': 'default'}}, 'eventId': 2, 'eventTimestamp': 1381095173.2539999, 'eventType': 'DecisionTaskScheduled'}, {'decisionTaskStartedEventAttributes': {'scheduledEventId': 2}, 'eventId': 3, 'eventTimestamp': 1381095177.5439999, 'eventType': 'DecisionTaskStarted'}, {'decisionTaskCompletedEventAttributes': {'scheduledEventId': 2, 'startedEventId': 3}, 'eventId': 4, 'eventTimestamp': 1381095177.855, 'eventType': 'DecisionTaskCompleted'}, {'activityTaskScheduledEventAttributes': {'activityId': 'saying_hi', 'activityType': {'name': 'HelloWorld', 'version': '1.0'}, 'decisionTaskCompletedEventId': 4, 'heartbeatTimeout': '600', 'scheduleToCloseTimeout': '3900', 'scheduleToStartTimeout': '300', 'startToCloseTimeout': '3600', 'taskList': {'name': 'default'}}, 'eventId': 5, 'eventTimestamp': 1381095177.855, 'eventType': 'ActivityTaskScheduled'}, {'activityTaskStartedEventAttributes': {'scheduledEventId': 5}, 'eventId': 6, 'eventTimestamp': 1381095179.427, 'eventType': 'ActivityTaskStarted'}, {'activityTaskCompletedEventAttributes': {'scheduledEventId': 5, 'startedEventId': 6}, 'eventId': 7, 'eventTimestamp': 1381095179.6989999, 'eventType': 'ActivityTaskCompleted'}, {'decisionTaskScheduledEventAttributes': {'startToCloseTimeout': '300', 'taskList': {'name': 'default'}}, 'eventId': 8, 'eventTimestamp': 1381095179.6989999, 'eventType': 'DecisionTaskScheduled'}, {'decisionTaskStartedEventAttributes': {'scheduledEventId': 8}, 'eventId': 9, 'eventTimestamp': 1381095179.7420001, 'eventType': 'DecisionTaskStarted'}, {'decisionTaskCompletedEventAttributes': {'scheduledEventId': 8, 'startedEventId': 9}, 'eventId': 10, 'eventTimestamp': 1381095180.026, 'eventType': 'DecisionTaskCompleted'}, {'eventId': 11, 'eventTimestamp': 1381095180.026, 'eventType': 'WorkflowExecutionCompleted', 'workflowExecutionCompletedEventAttributes': {'decisionTaskCompletedEventId': 10}}]h7jŠh8h9h:jdh<}r{(jf‰jgXbashh>h?h@]hA]hB]hC]hD]uhEKÀhFhh1]r|hHXß >>> execution.history() [{'eventId': 1, 'eventTimestamp': 1381095173.2539999, 'eventType': 'WorkflowExecutionStarted', 'workflowExecutionStartedEventAttributes': {'childPolicy': 'TERMINATE', 'executionStartToCloseTimeout': '3600', 'parentInitiatedEventId': 0, 'taskList': {'name': 'default'}, 'taskStartToCloseTimeout': '300', 'workflowType': {'name': 'HelloWorkflow', 'version': '1.0'}}}, {'decisionTaskScheduledEventAttributes': {'startToCloseTimeout': '300', 'taskList': {'name': 'default'}}, 'eventId': 2, 'eventTimestamp': 1381095173.2539999, 'eventType': 'DecisionTaskScheduled'}, {'decisionTaskStartedEventAttributes': {'scheduledEventId': 2}, 'eventId': 3, 'eventTimestamp': 1381095177.5439999, 'eventType': 'DecisionTaskStarted'}, {'decisionTaskCompletedEventAttributes': {'scheduledEventId': 2, 'startedEventId': 3}, 'eventId': 4, 'eventTimestamp': 1381095177.855, 'eventType': 'DecisionTaskCompleted'}, {'activityTaskScheduledEventAttributes': {'activityId': 'saying_hi', 'activityType': {'name': 'HelloWorld', 'version': '1.0'}, 'decisionTaskCompletedEventId': 4, 'heartbeatTimeout': '600', 'scheduleToCloseTimeout': '3900', 'scheduleToStartTimeout': '300', 'startToCloseTimeout': '3600', 'taskList': {'name': 'default'}}, 'eventId': 5, 'eventTimestamp': 1381095177.855, 'eventType': 'ActivityTaskScheduled'}, {'activityTaskStartedEventAttributes': {'scheduledEventId': 5}, 'eventId': 6, 'eventTimestamp': 1381095179.427, 'eventType': 'ActivityTaskStarted'}, {'activityTaskCompletedEventAttributes': {'scheduledEventId': 5, 'startedEventId': 6}, 'eventId': 7, 'eventTimestamp': 1381095179.6989999, 'eventType': 'ActivityTaskCompleted'}, {'decisionTaskScheduledEventAttributes': {'startToCloseTimeout': '300', 'taskList': {'name': 'default'}}, 'eventId': 8, 'eventTimestamp': 1381095179.6989999, 'eventType': 'DecisionTaskScheduled'}, {'decisionTaskStartedEventAttributes': {'scheduledEventId': 8}, 'eventId': 9, 'eventTimestamp': 1381095179.7420001, 'eventType': 'DecisionTaskStarted'}, {'decisionTaskCompletedEventAttributes': {'scheduledEventId': 8, 'startedEventId': 9}, 'eventId': 10, 'eventTimestamp': 1381095180.026, 'eventType': 'DecisionTaskCompleted'}, {'eventId': 11, 'eventTimestamp': 1381095180.026, 'eventType': 'WorkflowExecutionCompleted', 'workflowExecutionCompletedEventAttributes': {'decisionTaskCompletedEventId': 10}}]r}…r~}r(h6Uh7jyubaubeubhL)r€}r(h6Uh7hMh8h9h:hOh<}r‚(hB]hC]hA]h@]rƒh,ahD]r„hauhEMhFhh1]r…(hT)r†}r‡(h6XSerial Activity Executionrˆh7j€h8h9h:hXh<}r‰(hB]hC]hA]h@]hD]uhEMhFhh1]rŠhHXSerial Activity Executionr‹…rŒ}r(h6jˆh7j†ubaubh^)rŽ}r(h6X]The following example implements a basic workflow with activities executed one after another.rh7j€h8h9h:hbh<}r‘(hB]hC]hA]h@]hD]uhEMhFhh1]r’hHX]The following example implements a basic workflow with activities executed one after another.r“…r”}r•(h6jh7jŽubaubh^)r–}r—(h6XWThe business logic, i.e. the serial execution of activities, is encoded in the decider:r˜h7j€h8h9h:hbh<}r™(hB]hC]hA]h@]hD]uhEM hFhh1]ršhHXWThe business logic, i.e. the serial execution of activities, is encoded in the decider:r›…rœ}r(h6j˜h7j–ubaubja)rž}rŸ(h6X– # serial_decider.py import time import boto.swf.layer2 as swf class SerialDecider(swf.Decider): domain = 'boto_tutorial' task_list = 'default_tasks' version = '1.0' def run(self): history = self.poll() if 'events' in history: # Get a list of non-decision events to see what event came in last. workflow_events = [e for e in history['events'] if not e['eventType'].startswith('Decision')] decisions = swf.Layer1Decisions() # Record latest non-decision event. last_event = workflow_events[-1] last_event_type = last_event['eventType'] if last_event_type == 'WorkflowExecutionStarted': # Schedule the first activity. decisions.schedule_activity_task('%s-%i' % ('ActivityA', time.time()), 'ActivityA', self.version, task_list='a_tasks') elif last_event_type == 'ActivityTaskCompleted': # Take decision based on the name of activity that has just completed. # 1) Get activity's event id. last_event_attrs = last_event['activityTaskCompletedEventAttributes'] completed_activity_id = last_event_attrs['scheduledEventId'] - 1 # 2) Extract its name. activity_data = history['events'][completed_activity_id] activity_attrs = activity_data['activityTaskScheduledEventAttributes'] activity_name = activity_attrs['activityType']['name'] # 3) Optionally, get the result from the activity. result = last_event['activityTaskCompletedEventAttributes'].get('result') # Take the decision. if activity_name == 'ActivityA': decisions.schedule_activity_task('%s-%i' % ('ActivityB', time.time()), 'ActivityB', self.version, task_list='b_tasks', input=result) if activity_name == 'ActivityB': decisions.schedule_activity_task('%s-%i' % ('ActivityC', time.time()), 'ActivityC', self.version, task_list='c_tasks', input=result) elif activity_name == 'ActivityC': # Final activity completed. We're done. decisions.complete_workflow_execution() self.complete(decisions=decisions) return Trueh7j€h8h9h:jdh<}r (jf‰jgXpythonh>h?h@]hA]hB]hC]hD]uhEM hFhh1]r¡hHX– # serial_decider.py import time import boto.swf.layer2 as swf class SerialDecider(swf.Decider): domain = 'boto_tutorial' task_list = 'default_tasks' version = '1.0' def run(self): history = self.poll() if 'events' in history: # Get a list of non-decision events to see what event came in last. workflow_events = [e for e in history['events'] if not e['eventType'].startswith('Decision')] decisions = swf.Layer1Decisions() # Record latest non-decision event. last_event = workflow_events[-1] last_event_type = last_event['eventType'] if last_event_type == 'WorkflowExecutionStarted': # Schedule the first activity. decisions.schedule_activity_task('%s-%i' % ('ActivityA', time.time()), 'ActivityA', self.version, task_list='a_tasks') elif last_event_type == 'ActivityTaskCompleted': # Take decision based on the name of activity that has just completed. # 1) Get activity's event id. last_event_attrs = last_event['activityTaskCompletedEventAttributes'] completed_activity_id = last_event_attrs['scheduledEventId'] - 1 # 2) Extract its name. activity_data = history['events'][completed_activity_id] activity_attrs = activity_data['activityTaskScheduledEventAttributes'] activity_name = activity_attrs['activityType']['name'] # 3) Optionally, get the result from the activity. result = last_event['activityTaskCompletedEventAttributes'].get('result') # Take the decision. if activity_name == 'ActivityA': decisions.schedule_activity_task('%s-%i' % ('ActivityB', time.time()), 'ActivityB', self.version, task_list='b_tasks', input=result) if activity_name == 'ActivityB': decisions.schedule_activity_task('%s-%i' % ('ActivityC', time.time()), 'ActivityC', self.version, task_list='c_tasks', input=result) elif activity_name == 'ActivityC': # Final activity completed. We're done. decisions.complete_workflow_execution() self.complete(decisions=decisions) return Truer¢…r£}r¤(h6Uh7jžubaubh^)r¥}r¦(h6X7The workers only need to know which task lists to poll.r§h7j€h8h9h:hbh<}r¨(hB]hC]hA]h@]hD]uhEM?hFhh1]r©hHX7The workers only need to know which task lists to poll.rª…r«}r¬(h6j§h7j¥ubaubja)r­}r®(h6Xp# serial_worker.py import time import boto.swf.layer2 as swf class MyBaseWorker(swf.ActivityWorker): domain = 'boto_tutorial' version = '1.0' task_list = None def run(self): activity_task = self.poll() if 'activityId' in activity_task: # Get input. # Get the method for the requested activity. try: print 'working on activity from tasklist %s at %i' % (self.task_list, time.time()) self.activity(activity_task.get('input')) except Exception, error: self.fail(reason=str(error)) raise error return True def activity(self, activity_input): raise NotImplementedError class WorkerA(MyBaseWorker): task_list = 'a_tasks' def activity(self, activity_input): self.complete(result="Now don't be givin him sambuca!") class WorkerB(MyBaseWorker): task_list = 'b_tasks' def activity(self, activity_input): self.complete() class WorkerC(MyBaseWorker): task_list = 'c_tasks' def activity(self, activity_input): self.complete()h7j€h8h9h:jdh<}r¯(jf‰jgXpythonh>h?h@]hA]hB]hC]hD]uhEMAhFhh1]r°hHXp# serial_worker.py import time import boto.swf.layer2 as swf class MyBaseWorker(swf.ActivityWorker): domain = 'boto_tutorial' version = '1.0' task_list = None def run(self): activity_task = self.poll() if 'activityId' in activity_task: # Get input. # Get the method for the requested activity. try: print 'working on activity from tasklist %s at %i' % (self.task_list, time.time()) self.activity(activity_task.get('input')) except Exception, error: self.fail(reason=str(error)) raise error return True def activity(self, activity_input): raise NotImplementedError class WorkerA(MyBaseWorker): task_list = 'a_tasks' def activity(self, activity_input): self.complete(result="Now don't be givin him sambuca!") class WorkerB(MyBaseWorker): task_list = 'b_tasks' def activity(self, activity_input): self.complete() class WorkerC(MyBaseWorker): task_list = 'c_tasks' def activity(self, activity_input): self.complete()r±…r²}r³(h6Uh7j­ubaubh^)r´}rµ(h6X1Spin up a workflow execution and run the decider:r¶h7j€h8h9h:hbh<}r·(hB]hC]hA]h@]hD]uhEMnhFhh1]r¸hHX1Spin up a workflow execution and run the decider:r¹…rº}r»(h6j¶h7j´ubaubja)r¼}r½(h6X°$ python >>> import boto.swf.layer2 as swf >>> execution = swf.WorkflowType(name='SerialWorkflow', domain='boto_tutorial', version='1.0', task_list='default_tasks').start() >>>h7j€h8h9h:jdh<}r¾(jf‰jgXbashh>h?h@]hA]hB]hC]hD]uhEMphFhh1]r¿hHX°$ python >>> import boto.swf.layer2 as swf >>> execution = swf.WorkflowType(name='SerialWorkflow', domain='boto_tutorial', version='1.0', task_list='default_tasks').start() >>>rÀ…rÁ}rÂ(h6Uh7j¼ubaubja)rÃ}rÄ(h6XG$ python -i serial_decider.py >>> while SerialDecider().run(): pass ...h7j€h8h9h:jdh<}rÅ(jf‰jgXbashh>h?h@]hA]hB]hC]hD]uhEMwhFhh1]rÆhHXG$ python -i serial_decider.py >>> while SerialDecider().run(): pass ...rÇ…rÈ}rÉ(h6Uh7jÃubaubh^)rÊ}rË(h6X:Run the workers. The activities will be executed in order:rÌh7j€h8h9h:hbh<}rÍ(hB]hC]hA]h@]hD]uhEM~hFhh1]rÎhHX:Run the workers. The activities will be executed in order:rÏ…rÐ}rÑ(h6jÌh7jÊubaubja)rÒ}rÓ(h6Xx$ python -i serial_worker.py >>> while WorkerA().run(): pass ... working on activity from tasklist a_tasks at 1382046291h7j€h8h9h:jdh<}rÔ(jf‰jgXbashh>h?h@]hA]hB]hC]hD]uhEM€hFhh1]rÕhHXx$ python -i serial_worker.py >>> while WorkerA().run(): pass ... working on activity from tasklist a_tasks at 1382046291rÖ…r×}rØ(h6Uh7jÒubaubja)rÙ}rÚ(h6Xx$ python -i serial_worker.py >>> while WorkerB().run(): pass ... working on activity from tasklist b_tasks at 1382046541h7j€h8h9h:jdh<}rÛ(jf‰jgXbashh>h?h@]hA]hB]hC]hD]uhEM‡hFhh1]rÜhHXx$ python -i serial_worker.py >>> while WorkerB().run(): pass ... working on activity from tasklist b_tasks at 1382046541rÝ…rÞ}rß(h6Uh7jÙubaubja)rà}rá(h6Xx$ python -i serial_worker.py >>> while WorkerC().run(): pass ... working on activity from tasklist c_tasks at 1382046560h7j€h8h9h:jdh<}râ(jf‰jgXbashh>h?h@]hA]hB]hC]hD]uhEMŽhFhh1]rãhHXx$ python -i serial_worker.py >>> while WorkerC().run(): pass ... working on activity from tasklist c_tasks at 1382046560rä…rå}ræ(h6Uh7jàubaubh^)rç}rè(h6XTLooks good. Now, do the following to inspect the state and history of the execution:réh7j€h8h9h:hbh<}rê(hB]hC]hA]h@]hD]uhEM–hFhh1]rëhHXTLooks good. Now, do the following to inspect the state and history of the execution:rì…rí}rî(h6jéh7jçubaubja)rï}rð(h6Xÿ>>> execution.describe() {'executionConfiguration': {'childPolicy': 'TERMINATE', 'executionStartToCloseTimeout': '3600', 'taskList': {'name': 'default_tasks'}, 'taskStartToCloseTimeout': '300'}, 'executionInfo': {'cancelRequested': False, 'closeStatus': 'COMPLETED', 'closeTimestamp': 1382046560.901, 'execution': {'runId': '12fQ1zSaLmI5+lLXB8ux+8U+hLOnnXNZCY9Zy+ZvXgzhE=', 'workflowId': 'SerialWorkflow-1.0-1382046514'}, 'executionStatus': 'CLOSED', 'startTimestamp': 1382046514.994, 'workflowType': {'name': 'SerialWorkflow', 'version': '1.0'}}, 'latestActivityTaskTimestamp': 1382046560.632, 'openCounts': {'openActivityTasks': 0, 'openChildWorkflowExecutions': 0, 'openDecisionTasks': 0, 'openTimers': 0}} >>> execution.history() ...h7j€h8h9h:jdh<}rñ(jf‰jgXpythonh>h?h@]hA]hB]hC]hD]uhEM˜hFhh1]ròhHXÿ>>> execution.describe() {'executionConfiguration': {'childPolicy': 'TERMINATE', 'executionStartToCloseTimeout': '3600', 'taskList': {'name': 'default_tasks'}, 'taskStartToCloseTimeout': '300'}, 'executionInfo': {'cancelRequested': False, 'closeStatus': 'COMPLETED', 'closeTimestamp': 1382046560.901, 'execution': {'runId': '12fQ1zSaLmI5+lLXB8ux+8U+hLOnnXNZCY9Zy+ZvXgzhE=', 'workflowId': 'SerialWorkflow-1.0-1382046514'}, 'executionStatus': 'CLOSED', 'startTimestamp': 1382046514.994, 'workflowType': {'name': 'SerialWorkflow', 'version': '1.0'}}, 'latestActivityTaskTimestamp': 1382046560.632, 'openCounts': {'openActivityTasks': 0, 'openChildWorkflowExecutions': 0, 'openDecisionTasks': 0, 'openTimers': 0}} >>> execution.history() ...ró…rô}rõ(h6Uh7jïubaubeubhL)rö}r÷(h6Uh7hMh8h9h:hOh<}rø(hB]hC]hA]h@]rùh0ahD]rúhauhEM°hFhh1]rû(hT)rü}rý(h6XParallel Activity Executionrþh7jöh8h9h:hXh<}rÿ(hB]hC]hA]h@]hD]uhEM°hFhh1]rhHXParallel Activity Executionr…r}r(h6jþh7jüubaubh^)r}r(h6X_When activities are independent from one another, their execution may be scheduled in parallel.rh7jöh8h9h:hbh<}r(hB]hC]hA]h@]hD]uhEM²hFhh1]rhHX_When activities are independent from one another, their execution may be scheduled in parallel.r …r }r (h6jh7jubaubh^)r }r (h6XThe decider schedules all activities at once and marks progress until all activities are completed, at which point the workflow is completed.rh7jöh8h9h:hbh<}r(hB]hC]hA]h@]hD]uhEM´hFhh1]rhHXThe decider schedules all activities at once and marks progress until all activities are completed, at which point the workflow is completed.r…r}r(h6jh7j ubaubja)r}r(h6X&# parallel_decider.py import boto.swf.layer2 as swf import time SCHED_COUNT = 5 class ParallelDecider(swf.Decider): domain = 'boto_tutorial' task_list = 'default' def run(self): decision_task = self.poll() if 'events' in decision_task: decisions = swf.Layer1Decisions() # Decision* events are irrelevant here and can be ignored. workflow_events = [e for e in decision_task['events'] if not e['eventType'].startswith('Decision')] # Record latest non-decision event. last_event = workflow_events[-1] last_event_type = last_event['eventType'] if last_event_type == 'WorkflowExecutionStarted': # At start, kickoff SCHED_COUNT activities in parallel. for i in range(SCHED_COUNT): decisions.schedule_activity_task('activity%i' % i, 'ActivityA', '1.0', task_list=self.task_list) elif last_event_type == 'ActivityTaskCompleted': # Monitor progress. When all activities complete, complete workflow. completed_count = sum([1 for a in decision_task['events'] if a['eventType'] == 'ActivityTaskCompleted']) print '%i/%i' % (completed_count, SCHED_COUNT) if completed_count == SCHED_COUNT: decisions.complete_workflow_execution() self.complete(decisions=decisions) return Trueh7jöh8h9h:jdh<}r(jf‰jgXpythonh>h?h@]hA]hB]hC]hD]uhEM¶hFhh1]rhHX&# parallel_decider.py import boto.swf.layer2 as swf import time SCHED_COUNT = 5 class ParallelDecider(swf.Decider): domain = 'boto_tutorial' task_list = 'default' def run(self): decision_task = self.poll() if 'events' in decision_task: decisions = swf.Layer1Decisions() # Decision* events are irrelevant here and can be ignored. workflow_events = [e for e in decision_task['events'] if not e['eventType'].startswith('Decision')] # Record latest non-decision event. last_event = workflow_events[-1] last_event_type = last_event['eventType'] if last_event_type == 'WorkflowExecutionStarted': # At start, kickoff SCHED_COUNT activities in parallel. for i in range(SCHED_COUNT): decisions.schedule_activity_task('activity%i' % i, 'ActivityA', '1.0', task_list=self.task_list) elif last_event_type == 'ActivityTaskCompleted': # Monitor progress. When all activities complete, complete workflow. completed_count = sum([1 for a in decision_task['events'] if a['eventType'] == 'ActivityTaskCompleted']) print '%i/%i' % (completed_count, SCHED_COUNT) if completed_count == SCHED_COUNT: decisions.complete_workflow_execution() self.complete(decisions=decisions) return Truer…r}r(h6Uh7jubaubh^)r}r(h6XMAgain, the only bit of information a worker needs is which task list to poll.rh7jöh8h9h:hbh<}r(hB]hC]hA]h@]hD]uhEMÜhFhh1]rhHXMAgain, the only bit of information a worker needs is which task list to poll.r …r!}r"(h6jh7jubaubja)r#}r$(h6X­# parallel_worker.py import time import boto.swf.layer2 as swf class ParallelWorker(swf.ActivityWorker): domain = 'boto_tutorial' task_list = 'default' def run(self): """Report current time.""" activity_task = self.poll() if 'activityId' in activity_task: print 'working on', activity_task['activityId'] self.complete(result=str(time.time())) return Trueh7jöh8h9h:jdh<}r%(jf‰jgXpythonh>h?h@]hA]hB]hC]hD]uhEMÞhFhh1]r&hHX­# parallel_worker.py import time import boto.swf.layer2 as swf class ParallelWorker(swf.ActivityWorker): domain = 'boto_tutorial' task_list = 'default' def run(self): """Report current time.""" activity_task = self.poll() if 'activityId' in activity_task: print 'working on', activity_task['activityId'] self.complete(result=str(time.time())) return Truer'…r(}r)(h6Uh7j#ubaubh^)r*}r+(h6X1Spin up a workflow execution and run the decider:r,h7jöh8h9h:hbh<}r-(hB]hC]hA]h@]hD]uhEMñhFhh1]r.hHX1Spin up a workflow execution and run the decider:r/…r0}r1(h6j,h7j*ubaubja)r2}r3(h6XÙ$ python -i parallel_decider.py >>> execution = swf.WorkflowType(name='ParallelWorkflow', domain='boto_tutorial', version='1.0', task_list='default').start() >>> while ParallelDecider().run(): pass ... 1/5 2/5 4/5 5/5h7jöh8h9h:jdh<}r4(jf‰jgXbashh>h?h@]hA]hB]hC]hD]uhEMóhFhh1]r5hHXÙ$ python -i parallel_decider.py >>> execution = swf.WorkflowType(name='ParallelWorkflow', domain='boto_tutorial', version='1.0', task_list='default').start() >>> while ParallelDecider().run(): pass ... 1/5 2/5 4/5 5/5r6…r7}r8(h6Uh7j2ubaubh^)r9}r:(h6XURun two or more workers to see how the service partitions work execution in parallel.r;h7jöh8h9h:hbh<}r<(hB]hC]hA]h@]hD]uhEMþhFhh1]r=hHXURun two or more workers to see how the service partitions work execution in parallel.r>…r?}r@(h6j;h7j9ubaubja)rA}rB(h6Xˆ$ python -i parallel_worker.py >>> while ParallelWorker().run(): pass ... working on activity1 working on activity3 working on activity4h7jöh8h9h:jdh<}rC(jf‰jgXbashh>h?h@]hA]hB]hC]hD]uhEMhFhh1]rDhHXˆ$ python -i parallel_worker.py >>> while ParallelWorker().run(): pass ... working on activity1 working on activity3 working on activity4rE…rF}rG(h6Uh7jAubaubja)rH}rI(h6Xs$ python -i parallel_worker.py >>> while ParallelWorker().run(): pass ... working on activity2 working on activity0h7jöh8h9h:jdh<}rJ(jf‰jgXbashh>h?h@]hA]hB]hC]hD]uhEM hFhh1]rKhHXs$ python -i parallel_worker.py >>> while ParallelWorker().run(): pass ... working on activity2 working on activity0rL…rM}rN(h6Uh7jHubaubh^)rO}rP(h6XHAs seen above, the work was partitioned between the two running workers.rQh7jöh8h9h:hbh<}rR(hB]hC]hA]h@]hD]uhEMhFhh1]rShHXHAs seen above, the work was partitioned between the two running workers.rT…rU}rV(h6jQh7jOubaubeubhL)rW}rX(h6Uh7hMh8h9h:hOh<}rY(hB]hC]hA]h@]rZh'ahD]r[h auhEMhFhh1]r\(hT)r]}r^(h6X Sub-Workflowsr_h7jWh8h9h:hXh<}r`(hB]hC]hA]h@]hD]uhEMhFhh1]rahHX Sub-Workflowsrb…rc}rd(h6j_h7j]ubaubh^)re}rf(h6XTSometimes it's desired or necessary to break the process up into multiple workflows.rgh7jWh8h9h:hbh<}rh(hB]hC]hA]h@]hD]uhEMhFhh1]rihHXTSometimes it's desired or necessary to break the process up into multiple workflows.rj…rk}rl(h6jgh7jeubaubh^)rm}rn(h6XSince the decider is stateless, it's up to you to determine which workflow is being used and which action you would like to take.roh7jWh8h9h:hbh<}rp(hB]hC]hA]h@]hD]uhEMhFhh1]rqhHXSince the decider is stateless, it's up to you to determine which workflow is being used and which action you would like to take.rr…rs}rt(h6joh7jmubaubja)ru}rv(h6X< import boto.swf.layer2 as swf class SubWorkflowDecider(swf.Decider): domain = 'boto_tutorial' task_list = 'default' version = '1.0' def run(self): history = self.poll() events = [] if 'events' in history: events = history['events'] # Collect the entire history if there are enough events to become paginated while 'nextPageToken' in history: history = self.poll(next_page_token=history['nextPageToken']) if 'events' in history: events = events + history['events'] workflow_type = history['workflowType']['name'] # Get all of the relevent events that have happened since the last decision task was started workflow_events = [e for e in events if e['eventId'] > history['previousStartedEventId'] and not e['eventType'].startswith('Decision')] decisions = swf.Layer1Decisions() for event in workflow_events: last_event_type = event['eventType'] if last_event_type == 'WorkflowExecutionStarted': if workflow_type == 'SerialWorkflow': decisions.start_child_workflow_execution('SubWorkflow', self.version, "subworkflow_1", task_list=self.task_list, input="sub_1") elif workflow_type == 'SubWorkflow': for i in range(2): decisions.schedule_activity_task("activity_%d" % i, 'ActivityA', self.version, task_list='a_tasks') else: decisions.fail_workflow_execution(reason="Unknown workflow %s" % workflow_type) break elif last_event_type == 'ChildWorkflowExecutionCompleted': decisions.schedule_activity_task("activity_2", 'ActivityB', self.version, task_list='b_tasks') elif last_event_type == 'ActivityTaskCompleted': attrs = event['activityTaskCompletedEventAttributes'] activity = events[attrs['scheduledEventId'] - 1] activity_name = activity['activityTaskScheduledEventAttributes']['activityType']['name'] if activity_name == 'ActivityA': completed_count = sum([1 for a in events if a['eventType'] == 'ActivityTaskCompleted']) if completed_count == 2: # Complete the child workflow decisions.complete_workflow_execution() elif activity_name == 'ActivityB': # Complete the parent workflow decisions.complete_workflow_execution() self.complete(decisions=decisions) return Trueh7jWh8h9h:jdh<}rw(jf‰jgXpythonh>h?h@]hA]hB]hC]hD]uhEMhFhh1]rxhHX< import boto.swf.layer2 as swf class SubWorkflowDecider(swf.Decider): domain = 'boto_tutorial' task_list = 'default' version = '1.0' def run(self): history = self.poll() events = [] if 'events' in history: events = history['events'] # Collect the entire history if there are enough events to become paginated while 'nextPageToken' in history: history = self.poll(next_page_token=history['nextPageToken']) if 'events' in history: events = events + history['events'] workflow_type = history['workflowType']['name'] # Get all of the relevent events that have happened since the last decision task was started workflow_events = [e for e in events if e['eventId'] > history['previousStartedEventId'] and not e['eventType'].startswith('Decision')] decisions = swf.Layer1Decisions() for event in workflow_events: last_event_type = event['eventType'] if last_event_type == 'WorkflowExecutionStarted': if workflow_type == 'SerialWorkflow': decisions.start_child_workflow_execution('SubWorkflow', self.version, "subworkflow_1", task_list=self.task_list, input="sub_1") elif workflow_type == 'SubWorkflow': for i in range(2): decisions.schedule_activity_task("activity_%d" % i, 'ActivityA', self.version, task_list='a_tasks') else: decisions.fail_workflow_execution(reason="Unknown workflow %s" % workflow_type) break elif last_event_type == 'ChildWorkflowExecutionCompleted': decisions.schedule_activity_task("activity_2", 'ActivityB', self.version, task_list='b_tasks') elif last_event_type == 'ActivityTaskCompleted': attrs = event['activityTaskCompletedEventAttributes'] activity = events[attrs['scheduledEventId'] - 1] activity_name = activity['activityTaskScheduledEventAttributes']['activityType']['name'] if activity_name == 'ActivityA': completed_count = sum([1 for a in events if a['eventType'] == 'ActivityTaskCompleted']) if completed_count == 2: # Complete the child workflow decisions.complete_workflow_execution() elif activity_name == 'ActivityB': # Complete the parent workflow decisions.complete_workflow_execution() self.complete(decisions=decisions) return Truery…rz}r{(h6Uh7juubaubeubhL)r|}r}(h6Uh7hMh8h9h:hOh<}r~(hB]hC]hA]h@]rh)ahD]r€hauhEM[hFhh1]r(hT)r‚}rƒ(h6XMiscr„h7j|h8h9h:hXh<}r…(hB]hC]hA]h@]hD]uhEM[hFhh1]r†hHXMiscr‡…rˆ}r‰(h6j„h7j‚ubaubh^)rŠ}r‹(h6XSome of these things are not obvious by reading the API documents, so hopefully they help you avoid some time-consuming pitfalls.rŒh7j|h8h9h:hbh<}r(hB]hC]hA]h@]hD]uhEM]hFhh1]rŽhHXSome of these things are not obvious by reading the API documents, so hopefully they help you avoid some time-consuming pitfalls.r…r}r‘(h6jŒh7jŠubaubhL)r’}r“(h6Uh7j|h8h9h:hOh<}r”(hB]hC]hA]h@]r•h*ahD]r–hauhEMahFhh1]r—(hT)r˜}r™(h6X Paginationršh7j’h8h9h:hXh<}r›(hB]hC]hA]h@]hD]uhEMahFhh1]rœhHX Paginationr…rž}rŸ(h6jšh7j˜ubaubh^)r }r¡(h6X`When the decider polls for new tasks, the maximum number of events it will return at a time is 100 (configurable to a smaller number, but not larger). When running a workflow, this number gets quickly exceeded. If it does, the decision task will contain a key ``nextPageToken`` which can be submit to the ``poll()`` call to get the next page of events.h7j’h8h9h:hbh<}r¢(hB]hC]hA]h@]hD]uhEMchFhh1]r£(hHXWhen the decider polls for new tasks, the maximum number of events it will return at a time is 100 (configurable to a smaller number, but not larger). When running a workflow, this number gets quickly exceeded. If it does, the decision task will contain a key r¤…r¥}r¦(h6XWhen the decider polls for new tasks, the maximum number of events it will return at a time is 100 (configurable to a smaller number, but not larger). When running a workflow, this number gets quickly exceeded. If it does, the decision task will contain a key h7j ubcdocutils.nodes literal r§)r¨}r©(h6X``nextPageToken``h<}rª(hB]hC]hA]h@]hD]uh7j h1]r«hHX nextPageTokenr¬…r­}r®(h6Uh7j¨ubah:Uliteralr¯ubhHX which can be submit to the r°…r±}r²(h6X which can be submit to the h7j ubj§)r³}r´(h6X ``poll()``h<}rµ(hB]hC]hA]h@]hD]uh7j h1]r¶hHXpoll()r·…r¸}r¹(h6Uh7j³ubah:j¯ubhHX% call to get the next page of events.rº…r»}r¼(h6X% call to get the next page of events.h7j ubeubja)r½}r¾(h6X3decision_task = self.poll() events = [] if 'events' in decision_task: events = decision_task['events'] while 'nextPageToken' in decision_task: decision_task = self.poll(next_page_token=decision_task['nextPageToken']) if 'events' in decision_task: events += decision_task['events']h7j’h8h9h:jdh<}r¿(jf‰jgXpythonh>h?h@]hA]hB]hC]hD]uhEMhhFhh1]rÀhHX3decision_task = self.poll() events = [] if 'events' in decision_task: events = decision_task['events'] while 'nextPageToken' in decision_task: decision_task = self.poll(next_page_token=decision_task['nextPageToken']) if 'events' in decision_task: events += decision_task['events']rÁ…rÂ}rÃ(h6Uh7j½ubaubh^)rÄ}rÅ(h6XTDepending on your workflow logic, you might not need to aggregate all of the events.rÆh7j’h8h9h:hbh<}rÇ(hB]hC]hA]h@]hD]uhEMthFhh1]rÈhHXTDepending on your workflow logic, you might not need to aggregate all of the events.rÉ…rÊ}rË(h6jÆh7jÄubaubeubhL)rÌ}rÍ(h6Uh7j|h8h9h:hOh<}rÎ(hB]hC]hA]h@]rÏh&ahD]rÐh auhEMwhFhh1]rÑ(hT)rÒ}rÓ(h6XDecision TasksrÔh7jÌh8h9h:hXh<}rÕ(hB]hC]hA]h@]hD]uhEMwhFhh1]rÖhHXDecision Tasksr×…rØ}rÙ(h6jÔh7jÒubaubh^)rÚ}rÛ(h6XßWhen first running deciders and activities, it may seem that the decider gets called for every event that an activity triggers; however, this is not the case. More than one event can happen between decision tasks. The decision task will contain a key ``previousStartedEventId`` that lets you know the ``eventId`` of the last DecisionTaskStarted event that was processed. Your script will need to handle all of the events that have happened since then, not just the last activity.h7jÌh8h9h:hbh<}rÜ(hB]hC]hA]h@]hD]uhEMyhFhh1]rÝ(hHXûWhen first running deciders and activities, it may seem that the decider gets called for every event that an activity triggers; however, this is not the case. More than one event can happen between decision tasks. The decision task will contain a key rÞ…rß}rà(h6XûWhen first running deciders and activities, it may seem that the decider gets called for every event that an activity triggers; however, this is not the case. More than one event can happen between decision tasks. The decision task will contain a key h7jÚubj§)rá}râ(h6X``previousStartedEventId``h<}rã(hB]hC]hA]h@]hD]uh7jÚh1]rähHXpreviousStartedEventIdrå…ræ}rç(h6Uh7jáubah:j¯ubhHX that lets you know the rè…ré}rê(h6X that lets you know the h7jÚubj§)rë}rì(h6X ``eventId``h<}rí(hB]hC]hA]h@]hD]uh7jÚh1]rîhHXeventIdrï…rð}rñ(h6Uh7jëubah:j¯ubhHX§ of the last DecisionTaskStarted event that was processed. Your script will need to handle all of the events that have happened since then, not just the last activity.rò…ró}rô(h6X§ of the last DecisionTaskStarted event that was processed. Your script will need to handle all of the events that have happened since then, not just the last activity.h7jÚubeubja)rõ}rö(h6X_workflow_events = [e for e in events if e['eventId'] > decision_task['previousStartedEventId']]h7jÌh8h9h:jdh<}r÷(jf‰jgXpythonh>h?h@]hA]hB]hC]hD]uhEMhFhh1]røhHX_workflow_events = [e for e in events if e['eventId'] > decision_task['previousStartedEventId']]rù…rú}rû(h6Uh7jõubaubh^)rü}rý(h6XøYou may also wish to still filter out tasks that start with 'Decision' or filter it in some other way that fulfills your needs. You will now have to iterate over the workflow_events list and respond to each event, as it may contain multiple events.rþh7jÌh8h9h:hbh<}rÿ(hB]hC]hA]h@]hD]uhEMƒhFhh1]rhHXøYou may also wish to still filter out tasks that start with 'Decision' or filter it in some other way that fulfills your needs. You will now have to iterate over the workflow_events list and respond to each event, as it may contain multiple events.r…r}r(h6jþh7jüubaubeubhL)r}r(h6Uh7j|h8h9h:hOh<}r(hB]hC]hA]h@]rh(ahD]rhauhEMˆhFhh1]r (hT)r }r (h6XFiltering Eventsr h7jh8h9h:hXh<}r (hB]hC]hA]h@]hD]uhEMˆhFhh1]rhHXFiltering Eventsr…r}r(h6j h7j ubaubh^)r}r(h6XÝWhen running many tasks in parallel, a common task is searching through the history to see how many events of a particular activity type started, completed, and/or failed. Some basic list comprehension makes this trivial.rh7jh8h9h:hbh<}r(hB]hC]hA]h@]hD]uhEMŠhFhh1]rhHXÝWhen running many tasks in parallel, a common task is searching through the history to see how many events of a particular activity type started, completed, and/or failed. Some basic list comprehension makes this trivial.r…r}r(h6jh7jubaubja)r}r(h6XSdef filter_completed_events(self, events, type): completed = [e for e in events if e['eventType'] == 'ActivityTaskCompleted'] orig = [events[e['activityTaskCompletedEventAttributes']['scheduledEventId']-1] for e in completed] return [e for e in orig if e['activityTaskScheduledEventAttributes']['activityType']['name'] == type]h7jh8h9h:jdh<}r(jf‰jgXpythonh>h?h@]hA]hB]hC]hD]uhEMŽhFhh1]rhHXSdef filter_completed_events(self, events, type): completed = [e for e in events if e['eventType'] == 'ActivityTaskCompleted'] orig = [events[e['activityTaskCompletedEventAttributes']['scheduledEventId']-1] for e in completed] return [e for e in orig if e['activityTaskScheduledEventAttributes']['activityType']['name'] == type]r…r}r (h6Uh7jubaubhh)r!}r"(h6Xc.. _Amazon SWF API Reference: http://docs.aws.amazon.com/amazonswf/latest/apireference/Welcome.htmlh7jh8h9h:hkh<}r#(hmXEhttp://docs.aws.amazon.com/amazonswf/latest/apireference/Welcome.htmlh@]r$h-ahA]hB]hC]hD]r%hauhEM•hFhh1]ubhh)r&}r'(h6XQ.. _StackOverflow questions: http://stackoverflow.com/questions/tagged/amazon-swfh7jh8h9h:hkh<}r((hmX4http://stackoverflow.com/questions/tagged/amazon-swfh@]r)h#ahA]hB]hC]hD]r*h auhEM–hFhh1]ubhh)r+}r,(h6XN.. _Miscellaneous Blog Articles: http://log.ooz.ie/search/label/SimpleWorkflowh7jh8h9h:hkh<}r-(hmX-http://log.ooz.ie/search/label/SimpleWorkflowh@]r.h"ahA]hB]hC]hD]r/hauhEM—hFhh1]ubeubeubeubeh6UU transformerr0NU footnote_refsr1}r2Urefnamesr3}r4Usymbol_footnotesr5]r6Uautofootnote_refsr7]r8Usymbol_footnote_refsr9]r:U citationsr;]r<hFhU current_liner=NUtransform_messagesr>]r?(cdocutils.nodes system_message r@)rA}rB(h6Uh<}rC(hB]UlevelKh@]hA]Usourceh9hC]hD]UlineK UtypeUINFOrDuh1]rEh^)rF}rG(h6Uh<}rH(hB]hC]hA]h@]hD]uh7jAh1]rIhHX4Hyperlink target "simpleworkflow" is not referenced.rJ…rK}rL(h6Uh7jFubah:hbubah:Usystem_messagerMubj@)rN}rO(h6Uh<}rP(hB]UlevelKh@]hA]Usourceh9hC]hD]UlineM•UtypejDuh1]rQh^)rR}rS(h6Uh<}rT(hB]hC]hA]h@]hD]uh7jNh1]rUhHX>Hyperlink target "amazon swf api reference" is not referenced.rV…rW}rX(h6Uh7jRubah:hbubah:jMubj@)rY}rZ(h6Uh<}r[(hB]UlevelKh@]hA]Usourceh9hC]hD]UlineM–UtypejDuh1]r\h^)r]}r^(h6Uh<}r_(hB]hC]hA]h@]hD]uh7jYh1]r`hHX=Hyperlink target "stackoverflow questions" is not referenced.ra…rb}rc(h6Uh7j]ubah:hbubah:jMubj@)rd}re(h6Uh<}rf(hB]UlevelKh@]hA]Usourceh9hC]hD]UlineM—UtypejDuh1]rgh^)rh}ri(h6Uh<}rj(hB]hC]hA]h@]hD]uh7jdh1]rkhHXAHyperlink target "miscellaneous blog articles" is not referenced.rl…rm}rn(h6Uh7jhubah:hbubah:jMubeUreporterroNUid_startrpKU autofootnotesrq]rrU citation_refsrs}rtUindirect_targetsru]rvUsettingsrw(cdocutils.frontend Values rxory}rz(Ufootnote_backlinksr{KUrecord_dependenciesr|NU rfc_base_urlr}Uhttp://tools.ietf.org/html/r~U tracebackrˆUpep_referencesr€NUstrip_commentsrNU toc_backlinksr‚UentryrƒU language_coder„Uenr…U datestampr†NU report_levelr‡KU _destinationrˆNU halt_levelr‰KU strip_classesrŠNhXNUerror_encoding_error_handlerr‹UbackslashreplacerŒUdebugrNUembed_stylesheetrŽ‰Uoutput_encoding_error_handlerrUstrictrU sectnum_xformr‘KUdump_transformsr’NU docinfo_xformr“KUwarning_streamr”NUpep_file_url_templater•Upep-%04dr–Uexit_status_levelr—KUconfigr˜NUstrict_visitorr™NUcloak_email_addressesršˆUtrim_footnote_reference_spacer›‰UenvrœNUdump_pseudo_xmlrNUexpose_internalsržNUsectsubtitle_xformrŸ‰U source_linkr NUrfc_referencesr¡NUoutput_encodingr¢Uutf-8r£U source_urlr¤NUinput_encodingr¥U utf-8-sigr¦U_disable_configr§NU id_prefixr¨UU tab_widthr©KUerror_encodingrªUUTF-8r«U_sourcer¬h9Ugettext_compactr­ˆU generatorr®NUdump_internalsr¯NU smart_quotesr°‰U pep_base_urlr±Uhttp://www.python.org/dev/peps/r²Usyntax_highlightr³Ulongr´Uinput_encoding_error_handlerrµjUauto_id_prefixr¶Uidr·Udoctitle_xformr¸‰Ustrip_elements_with_classesr¹NU _config_filesrº]Ufile_insertion_enabledr»ˆU raw_enabledr¼KU dump_settingsr½NubUsymbol_footnote_startr¾KUidsr¿}rÀ(h(jh*j’h"j+h/hMh$hih%jKh0jöh)j|h&jÌh.jŠh+hÏh!håh'jWh,j€h-j!h hph#j&uUsubstitution_namesrÁ}rÂh:hFh<}rÃ(hB]h@]hA]Usourceh9hC]hD]uU footnotesrÄ]rÅUrefidsrÆ}rÇub.