Package: actionpack
a lib for describing Actions and how they should be performedOverviewSide effects are annoying.
Verification of intended outcome is often difficult and can depend on the system's state at runtime.
Questions like"Is the file going to be present when data is written?"or"Will that service be available?"come to mind.
Keeping track of external system state is just impractical, but declaring intent and encapsulating its disposition is doable.UsageWhat are Actions for?Actionobjects are used to declare intent:>>>action=Read('path/to/some/file')Theaction, above, represents the intent toReadthe contents from the file at some path.
AnActioncan be "performed" and the result is captured by aResultobject:>>>result=action.perform()# produces a Result objectTheresultholds disposition information about the outcome of theaction.
That includes information likewhether or not it was.successfulor that it was.produced_atsome unix timestamp(microseconds by default).
To gain access to the value of theresult, check the.valueattribute.
If unsuccessful, there will be anException, otherwise there will be an instance of some non-Exceptiontype.Can Actions be connected?AResultcan be produced by performing anActionand that value can be percolated through a collection ofActionTypesusing thePipelineabstraction:>>>pipeline=Pipeline(ReadInput('Which file? '),Read)The above, is not the most helpful incantation, but toss the following in awhileloop and witness some REPL-like behavior (bonus points for feeding itactualfilenames/filepaths).result=Pipeline(ReadInput('Which file? '),Read).perform()print(result.value)SometimesActionTypes in aPipelinedon't "fit" together.
That's where thePipeline.Fittingcomes in:listen=ReadInput('What should I record? ')record=Pipeline.Fitting(action=Write,**{'prefix':f'[{datetime.now()}] ','append':True,'filename':filename,'to_write':Pipeline.Receiver},)Pipeline(listen,record).perform()⚠️NOTE:Writing to stdout is also possible using theWrite.STDOUTobject as a filename. How that works is an exercise left for the user.Handling multiple Actions at a timeAnActioncollection can be used to describe a procedure:actions=[action,Read('path/to/some/other/file'),ReadInput('>>> how goes? <<<\n> '),MakeRequest('GET','http://google.com'),RetryPolicy(MakeRequest('GET','http://bad-connectivity.com'),max_retries=2,delay_between_attempts=2)Write('path/to/yet/another/file','sup')]procedure=Procedure(actions)And aProcedurecan be executed synchronously or otherwise:results=procedure.execute()# synchronously by default_results=procedure.execute(synchronously=False)# async; not thread saferesult=next(results)print(result.value)AKeyedProcedureis just aProcedurecomprised of namedActions.
TheActionnames are used as keys for convenient result lookup.prompt='>>> sure, I'llsaveitforya..<<<\n>'saveme=ReadInput(prompt).set(name='saveme')writeme=Write('path/to/yet/another/file','sup').set(name='writeme')actions=[saveme,writeme]keyed_procedure=KeyedProcedure(actions)results=keyed_procedure.execute()keyed_results=dict(results)first,second=keyed_results.get('saveme'),keyed_results.get('writeme')⚠️NOTE:Procedureelements are evaluatedindependentlyunlike with aPipelinein which the result of performing anActionis passed to the nextActionType.For the honeybadgersOne can also create anActionfrom some arbitrary function>>>Call(closure=Closure(some_function,arg,kwarg=kwarg))DevelopmentSetupBuild scripting is managed vianoxfile.
Executenox -lto see the available commands (set theUSEVENVenvironment variable to view virtualenv-oriented commands).
To get started, simply runnox.
Doing so will installactionpackon your PYTHONPATH.
Using theUSEVENVenvironment variable, a virtualenv can be created in the local ".nox/" directory with something like:USEVENV=virtualenv nox -s actionpack-venv-install-3.10All tests can be run withnox -s testand a single test can be run with something like the following:TESTNAME=<tests-subdir>.<test-module>.<class-name>.<method-name> nox -s testCoverage reports are optional and can be disabled using theCOVERAGEenvironment variable set to a falsy value like "no".Homebrewed ActionsMaking newactionpack.actionsis straightforward.
After defining a class that inheritsAction, ensure it has an.instructionmethod.
If any attribute validation is desired, a.validatemethod can be added.There is no need to addActiondependencies tosetup.py.
Dependencies required for developing anActiongo in :::drum roll:::requirements.txt.
When declaring yourActionclass, arequiresparameter can be passed a tuple.classMakeRequest(Action,requires=('requests',)):...This will check if the dependencies are installed and, if so, will register each of them as class attributes.mr=MakeRequest('GET','http://localhost')mr.requests#=> <module 'requests' from '~/actionpack/actionpack-venv/lib/python3/site-packages/requests/__init__.py'>