{ "info": { "author": "Thomas Gaigher", "author_email": "info@pypyr.io", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Topic :: Software Development :: Build Tools", "Topic :: System :: Shells", "Topic :: Utilities" ], "description": "#########################\npypyr cli pipeline runner\n#########################\n\n.. image:: https://pypyr.io/images/pypyr-logo-small.png\n :alt: pypyr-logo\n :align: left\n\n*pypyr*\n pronounce how you like, but I generally say *piper* as in \"piping down the\n valleys wild\"\n\n\npypyr is a command line interface to run pipelines defined in yaml. Think of\npypyr as a simple task runner that lets you define and run sequential steps.\nLike a turbo-charged shell script, but less finicky.\n\nYou can run loops, conditionally execute steps based on conditions you specify,\nwait for status changes before continuing, break on failure conditions or\nswallow errors. Pretty useful for orchestrating continuous integration,\ncontinuous deployment and devops operations.\n\nRead, merge and write configuration files to and from yaml, json or just text.\n\n|build-status| |coverage| |pypi|\n\n.. contents::\n\n.. section-numbering::\n\n************\nInstallation\n************\n\npip\n===\n.. code-block:: bash\n\n $ pip install --upgrade pypyr\n\npython version\n==============\nTested against Python >=3.6\n\ndocker\n======\nStuck with an older version of python? Want to run pypyr in an environment that\nyou don't control, like a CI server somewhere?\n\nYou can use the official pypyr docker image as a drop-in replacement for the\npypyr executable. https://hub.docker.com/r/pypyr/pypyr/\n\n.. code-block:: bash\n\n $ docker run pypyr/pypyr echo \"Ceci n'est pas une pipe\"\n\n\n*****\nUsage\n*****\nRun your first pipeline\n=======================\nRun one of the built-in pipelines to get a feel for it:\n\n.. code-block:: bash\n\n $ pypyr echo \"Ceci n'est pas une pipe\"\n\nYou can achieve the same thing by running a pipeline where the context is set\nin the pipeline yaml rather than passed in as the 2nd positional argument:\n\n.. code-block:: bash\n\n $ pypyr magritte\n\nCheck here `pypyr.steps.echo`_ to see yaml that does this.\n\nRun a pipeline\n==============\npypyr runs the pipeline specified by the name that you pass to the cli.\n\nTo make your pipelines edit easier in your favorite yaml editor, use a .yaml\nextension, but to save on typing you don't need to enter the .yaml bit at the\ncommand line. You can use your usual directory separators if you're running\na pipeline in a sub-directory, like ``pypyr subdir/subsubdir/pipeline``\n\n.. code-block:: bash\n\n # run ./mypipelinename.yaml with DEBUG logging level\n $ pypyr mypipelinename --loglevel 10\n\n # run ./mypipelinename.yaml with INFO logging level.\n # log is an alias for loglevel, so less typing, wooohoo!\n $ pypyr mypipelinename --log 20\n\n # If you don't specify --loglevel it defaults to 25 - NOTIFY logging level.\n $ pypyr mypipelinename\n\n # run ./mydir/mypipelinename.yaml\n # The 2nd argument is any arbitrary sequence of strings, known as the input\n # context arguments.\n # For this input argument to be available\n # to your pipeline you need to specify a context parser in your pipeline yaml.\n $ pypyr mydir/mypipelinename arbitrary string here\n\n # run ./mypipelinename.yaml with an input context in key-value\n # pair format. For this input to be available to your pipeline you need to\n # specify a context_parser like pypyr.parser.keyvaluepairs in your\n # pipeline yaml.\n $ pypyr mypipelinename mykey=value anotherkey=anothervalue\n\n\nGet cli help\n============\npypyr has a couple of arguments and switches you might find useful. See them all\nhere:\n\n.. code-block:: bash\n\n $ pypyr -h\n\nExamples\n========\nIf you prefer reading code to reading words, https://github.com/pypyr/pypyr-example\n\n*********************************************\nPipeline directory locations look-up sequence\n*********************************************\npypyr looks for pipelines in a sequence where it searches different directories\nin a specific order. pypyr runs the 1st pipeline it finds in the look-up\nsequence.\n\nWorking dir is your current directory, unless you use the ``--dir`` flag to\ntell pypyr something different.\n\nAssuming you run ``pypyr pipeline-name``, this is the look-up sequence:\n\n1. {working dir}/{pipeline-name}.yaml\n2. {working dir}/pipelines/{pipeline-name}.yaml\n3. {pypyr install directory}/pipelines/{pipeline-name}.yaml\n\nThe last look-up is for pypyr built-in pipelines. You probably shouldn't be\nsaving your own pipelines there, they might get over-written or wiped by\nupgrades or re-installs.\n\n***************************\nAnatomy of a pypyr pipeline\n***************************\nPipeline yaml structure\n=======================\nA pipeline is a .yaml file. pypyr uses YAML version 1.2.\n\nSave pipelines wherever you please. To run a pipeline, execute\n``pypyr pipelinename`` from the directory where you saved ``pipelinename.yaml``\n\n.. code-block:: yaml\n\n # This is an example showing the anatomy of a pypyr pipeline\n # A pipeline should be saved as {working dir}/mypipelinename.yaml.\n # Run the pipeline from {working dir} like this: pypyr mypipelinename\n\n # optional\n context_parser: my.custom.parser\n\n # mandatory.\n steps:\n - my.package.my.module # simple step pointing at a python module in a package\n - mymodule # simple step pointing at a python file\n - name: my.package.another.module # complex step. It contains a description and in parameters.\n description: Optional description is for humans. It's any text that makes your life easier.\n in: # optional. In parameters are added to the context so that this step and subsequent steps can use these key-value pairs.\n parameter1: value1\n parameter2: value2\n run: True # optional. Runs this step if True, skips step if False. Defaults to True if not specified.\n skip: False # optional. Skips this step if True, runs step if False. Defaults to False if not specified.\n swallow: False # optional. Swallows any errors raised by the step. Defaults to False if not specified.\n\n # optional.\n on_success:\n - my.first.success.step\n - my.second.success.step\n\n # optional.\n on_failure:\n - my.failure.handler.step\n - my.failure.handler.notifier\n\n\nCustom step groups\n==================\npypyr looks for 3 different step groups on a default run:\n\n- steps\n- on_success\n- on_failure\n\n.. code-block:: yaml\n\n # the default pypyr step-groups\n steps: # 'steps' is the default step-group that runs\n - steps.step1 # will run ./steps/step1.py\n - arb.step2 # will run ./arb/step2.py\n\n on_success: # on_success executes when the pipeline completes successfully\n - success_step # will run ./success_step.py\n\n on_failure: # on_failure executes whenever pipeline processing hits an error\n - steps.failure_step # will run ./steps/failure_step.py\n\nYou don't have to stick to these default step-groups, though. You can specify\nyour own step-groups, or mix in your own step-groups with the defaults.\n\n.. code-block:: yaml\n\n # ./step-groups-example.yaml\n sg1:\n - name: pypyr.steps.echo\n in:\n echoMe: sg1.1\n - name: pypyr.steps.echo\n in:\n echoMe: sg1.2\n sg2:\n - name: pypyr.steps.echo\n in:\n echoMe: sg2.1\n - name: pypyr.steps.echo\n in:\n echoMe: sg2.2\n sg3:\n - name: pypyr.steps.echo\n in:\n echoMe: sg3.1\n - name: pypyr.steps.echo\n in:\n echoMe: sg3.2\n sg4:\n - name: pypyr.steps.echo\n in:\n echoMe: sg4.1\n - name: pypyr.steps.echo\n in:\n echoMe: sg4.2\n\nYou can use the ``--groups`` switch to specify which groups you want to run and\nin what order:\n\n``pypyr step-groups-example --groups sg2 sg1 sg3``\n\nIf you don't specify ``--groups`` pypyr will just look for the standard\n*steps* group as per usual. You can still call other step-groups from the\ndefault *steps* group, so you could think of *steps* a bit like the ``main()``\nentrypoint in traditional programming.\n\nControl-of-Flow\n---------------\nYou can control the flow of pypyr pipeline execution between step-groups with\nthe following handy steps:\n\n- `pypyr.steps.call`_\n- `pypyr.steps.jump`_\n- `pypyr.steps.stopstepgroup`_\n- `pypyr.steps.stoppipeline`_\n- `pypyr.steps.stop`_\n\nYou can call other pipelines from within a pipeline with:\n\n- `pypyr.steps.pype`_\n\nOn top of this, you can control which individual steps should run or not using\nthe conditional `Step decorators`_ :\n\n- ``run``\n- ``skip``\n\nLooping happens on the step-level, using the following `Step decorators`_ :\n\n- ``while``\n- ``foreach``\n\nYou can set a ``while`` or ``foreach`` loop on any given step, including on a\n`pypyr.steps.call`_ step or a `pypyr.steps.pype`_ step, which lets you call\nanother step-group or pipeline repeatedly in a loop.\n\nBuilt-in pipelines\n==================\n+-----------------------------+-------------------------------------------------+-------------------------------------------------------------------------------------+\n| **pipeline** | **description** | **how to run** |\n+-----------------------------+-------------------------------------------------+-------------------------------------------------------------------------------------+\n| donothing | Does what it says. Nothing. |``pypyr donothing`` |\n| | | |\n| | | |\n| | | |\n+-----------------------------+-------------------------------------------------+-------------------------------------------------------------------------------------+\n| echo | Echos context value echoMe to output. |``pypyr echo text goes here`` |\n+-----------------------------+-------------------------------------------------+-------------------------------------------------------------------------------------+\n| pypyrversion | Prints the python cli version number. |``pypyr pypyrversion`` |\n| | | |\n| | | |\n+-----------------------------+-------------------------------------------------+-------------------------------------------------------------------------------------+\n| magritte | Thoughts about pipes. |``pypyr magritte`` |\n| | | |\n| | | |\n+-----------------------------+-------------------------------------------------+-------------------------------------------------------------------------------------+\n\ncontext_parser\n==============\nOptional.\n\nA context_parser parses the pypyr command's context input arguments. This is\nall the positional arguments after the pipeline-name from the command line.\n\nThe chances are pretty good that the context_parser will take the context\ncommand arguments and put in into the pypyr context.\n\nThe pypyr context is a dictionary that is in scope for the duration of the entire\npipeline. The context_parser can initialize the context. Any step in the pipeline\ncan add, edit or remove items from the context dictionary.\n\nBuilt-in context parsers\n------------------------\n+-----------------------------+-------------------------------------------------+-------------------------------------------------------------------------------------+\n| **context parser** | **description** | **example input** |\n+-----------------------------+-------------------------------------------------+-------------------------------------------------------------------------------------+\n| pypyr.parser.dict | Takes a key=value pair string and returns a |``pypyr pipelinename param1=value1 param2=\"value 2\" param3=value3`` |\n| | dictionary where each pair becomes a dictionary | |\n| | element inside a dict with name *argDict*. |This will create a context dictionary like this: |\n| | | |\n| | Escape literal spaces with single or double |.. code-block:: python |\n| | quotes. | |\n| | | {'argDict': {'param1': 'value1', |\n| | | 'param2': 'value 2', |\n| | | 'param3': 'value3'}} |\n+-----------------------------+-------------------------------------------------+-------------------------------------------------------------------------------------+\n| pypyr.parser.json | Takes a json string and returns a dictionary. |``pypyr pipelinename {\"key1\":\"value1\",\"key2\":\"value2\"}`` |\n+-----------------------------+-------------------------------------------------+-------------------------------------------------------------------------------------+\n| pypyr.parser.jsonfile | Opens json file and returns a dictionary. |``pypyr pipelinename \"./path/sample.json\"`` |\n+-----------------------------+-------------------------------------------------+-------------------------------------------------------------------------------------+\n| pypyr.parser.keys | For each input argument, create a dictionary |``pypyr pipelinename param1 'par am2' param3`` |\n| | where each element becomes the key, with value | |\n| | set to true. |This will create a context dictionary like this: |\n| | | |\n| | Escape literal spaces with single or double |.. code-block:: python |\n| | quotes. | |\n| | | {'param1': True, 'par am2': True, 'param3': True} |\n+-----------------------------+-------------------------------------------------+-------------------------------------------------------------------------------------+\n| pypyr.parser.keyvaluepairs | Takes a key=value pair string and returns a |``pypyr pipelinename param1=value1 param2=value2 \"param 3\"=value3`` |\n| | dictionary where each pair becomes a dictionary | |\n| | element. |This will create a context dictionary like this: |\n| | | |\n| | Escape literal spaces with single or double |.. code-block:: python |\n| | quotes. | |\n| | | {'param1': 'value1', |\n| | | 'param2': 'value2', |\n| | | 'param 3': 'value3'} |\n+-----------------------------+-------------------------------------------------+-------------------------------------------------------------------------------------+\n| pypyr.parser.list | Takes the input arguments and returns a list in |``pypyr pipelinename param1 param2 param3`` |\n| | context with name *argList*. | |\n| | |This will create a context dictionary like this: |\n| | Escape literal spaces with single or double | |\n| | quotes. |.. code-block:: python |\n| | | |\n| | | {'argList': ['param1', 'param2', 'param3']} |\n+-----------------------------+-------------------------------------------------+-------------------------------------------------------------------------------------+\n| pypyr.parser.string | Takes any arbitrary input and returns a single |``pypyr pipelinename arbitrary string here`` |\n| | string in context with name *argString*. | |\n| | |This will create a context dictionary like this: |\n| | | |\n| | |.. code-block:: python |\n| | | |\n| | | {'argString': 'arbitrary string here'} |\n+-----------------------------+-------------------------------------------------+-------------------------------------------------------------------------------------+\n| pypyr.parser.yamlfile | Opens a yaml file and writes the contents into |``pypyr pipelinename ./path/sample.yaml`` |\n| | the pypyr context dictionary. | |\n| | | |\n| | The top (or root) level yaml should describe a | |\n| | map, not a sequence. | |\n| | | |\n| | Sequence (this won't work): | |\n| | | |\n| | .. code-block:: yaml | |\n| | | |\n| | - thing1 | |\n| | - thing2 | |\n| | | |\n| | Instead, do a map (aka dictionary): | |\n| | | |\n| | .. code-block:: yaml | |\n| | | |\n| | thing1: thing1value | |\n| | thing2: thing2value | |\n+-----------------------------+-------------------------------------------------+-------------------------------------------------------------------------------------+\n\n\nRoll your own context_parser\n----------------------------\n.. code-block:: python\n\n import logging\n\n\n # getLogger will grab the parent logger context, so your loglevel and\n # formatting will inherit correctly automatically from the pypyr core.\n logger = logging.getLogger(__name__)\n\n\n def get_parsed_context(args):\n \"\"\"This is the signature for a context parser.\n\n Args:\n args: list of string. Passed from command-line invocation where\n pypyr pipelinename this is the context_arg\n This would result in args == ['this', 'is', 'the', 'context_arg']\n\n Returns:\n dict. This dict will initialize the context for the pipeline run.\n \"\"\"\n assert args, (\"pipeline must be invoked with context arg set.\")\n logger.debug(\"starting\")\n\n # your clever code here. Chances are pretty good you'll be doing things\n # with the input args list to create a dictionary.\n\n # function signature returns a dictionary\n return {'key1': 'value1', 'key2':'value2'}\n\nsteps\n=====\nMandatory.\n\nsteps is a list of steps to execute in sequence. A step is simply a bit of\npython that does stuff.\n\nYou can specify a step in the pipeline yaml in two ways:\n\n* Simple step\n\n - a simple step is just the name of the python module.\n\n - pypyr will look in your working directory for these modules or packages.\n\n - For a package, be sure to specify the full namespace (i.e not just `mymodule`, but `mypackage.mymodule`).\n\n .. code-block:: yaml\n\n steps:\n - my.package.my.module # points at a python module in a package.\n - mymodule # simple step pointing at a python file\n\n* Complex step\n\n - a complex step allows you to specify a few more details for your step, but at heart it's the same thing as a simple step - it points at some python.\n\n .. code-block:: yaml\n\n steps:\n - name: my.package.another.module\n description: Optional Description is for humans.\n It is any yaml-escaped text that makes your life easier.\n Outputs to the console during runtime as INFO.\n comment: Optional comments for pipeline developers.\n Does not output to console during run-time.\n in: #optional. In parameters are added to the context so that this step and subsequent steps can use these key-value pairs.\n parameter1: value1\n parameter2: value2\n\n\n* You can freely mix and match simple and complex steps in the same pipeline.\n\n* Frankly, the only reason simple steps are there is because I'm lazy and I dislike redundant typing.\n\nStep decorators\n---------------\nDecorators overview\n^^^^^^^^^^^^^^^^^^^\nComplex steps have various optional step decorators that change how or if a step is run.\n\nDon't bother specifying these unless you want to deviate from the default values.\n\n\n.. code-block:: yaml\n\n steps:\n - name: my.package.another.module\n description: Optional Description is for humans.\n Any yaml-escaped text that makes your life easier.\n Outputs to console during run-time.\n comment: Optional comments for pipeline developers. Like code comments.\n Does not output to console during run.\n in: # optional. In parameters are added to the context.\n # this step and subsequent steps can use these key-value pairs.\n parameter1: value1\n parameter2: value2\n foreach: [] # optional. Repeat the step once for each item in this list.\n onError: # optional. Custom Error Info to add to error if step fails.\n code: 111 # you can also use custom elements for your custom error.\n description: arb description here\n retry: # optional. Retry step until it doesn't raise an error.\n max: 1 # max times to retry. integer. Defaults None (infinite).\n sleep: 0 # sleep between retries, in seconds. Decimals allowed. Defaults 0.\n stopOn: ['ValueError', 'MyModule.SevereError'] # Stop retry on these errors. Defaults None (retry all).\n retryOn: ['TimeoutError'] # Only retry these errors. Defaults None (retry all).\n run: True # optional. Runs this step if True, skips step if False. Defaults to True if not specified.\n skip: False # optional. Skips this step if True, runs step if False. Defaults to False if not specified.\n swallow: False # optional. Swallows any errors raised by the step. Defaults to False if not specified.\n while: # optional. repeat step until stop is True or max iterations reached.\n stop: '{keyhere}' # loop until this evaluates True.\n max: 1 # max loop iterations to run. integer. Defaults None (infinite).\n sleep: 0 # sleep between iterations, in seconds. Decimals allowed. Defaults 0.\n errorOnMax: False # raise error if max reached. Defaults False.\n\n+---------------+----------+---------------------------------------------+----------------+\n| **decorator** | **type** | **description** | **default** |\n+---------------+----------+---------------------------------------------+----------------+\n| foreach | list | Run the step once for each item in the list.| None |\n| | | The iterator is ``context['i']``. | |\n| | | | |\n| | | The *run*, *skip* & *swallow* decorators | |\n| | | evaluate dynamically on each iteration. | |\n| | | So if during an iteration the step's logic | |\n| | | sets ``run=False``, the step will not | |\n| | | execute on the next iteration. | |\n+---------------+----------+---------------------------------------------+----------------+\n| in | dict | Add this to the context so that this | None |\n| | | step and subsequent steps can use these | |\n| | | key-value pairs. | |\n| | | | |\n| | | *in* evaluates once at the beginning of step| |\n| | | execution, before the *foreach* and *while* | |\n| | | decorators. It does not re-evaluate for each| |\n| | | loop iteration. | |\n+---------------+----------+---------------------------------------------+----------------+\n| onError | any | If this step errors, write the contents of | None |\n| | | *onError* to *runErrors.customError* in | |\n| | | context. Subsequent steps can then use this | |\n| | | information, assuming you've got a *swallow*| |\n| | | somewhere in the call chain. | |\n| | | | |\n| | | *onError* can be a simple string, or your | |\n| | | your own dict, or any given object. You can | |\n| | | use `Substitutions`_. | |\n+---------------+----------+---------------------------------------------+----------------+\n| retry | dict | Retries the step until it doesn't error. | None |\n| | | The retry iteration counter is | |\n| | | ``context['retryCounter']``. | |\n| | | | |\n| | | If you reach *max* while the step still | |\n| | | errors, will raise the last error and stop | |\n| | | further pipeline processing, unless | |\n| | | *swallow* is True. | |\n| | | | |\n| | | When neither *stopOn* and *retryOn* set, | |\n| | | all types of errors will retry. | |\n| | | | |\n| | | If *stopOn* is specified, errors listed | |\n| | | in *stopOn* will stop retry processing and | |\n| | | raise an error. Errors not listed in | |\n| | | *stopOn* will retry. | |\n| | | | |\n| | | If *retryOn* is specified, ONLY errors | |\n| | | listed in *retryOn* will retry. | |\n| | | | |\n| | | *max* evaluates before *stopOn* and | |\n| | | *retryOn*. *stopOn* supersedes *retryOn*. | |\n| | | | |\n| | | For builtin python errors, specify the bare | |\n| | | error name for *stopOn* and *retryOn*, e.g | |\n| | | 'ValueError', 'KeyError'. | |\n| | | | |\n| | | For all other errors, use module.errorname, | |\n| | | e.g 'mypackage.mymodule.myerror' | |\n+---------------+----------+---------------------------------------------+----------------+\n| run | bool | Runs this step if True, skips step if | True |\n| | | False. | |\n+---------------+----------+---------------------------------------------+----------------+\n| skip | bool | Skips this step if True, runs step if | False |\n| | | False. Evaluates after the *run* decorator. | |\n| | | | |\n| | | If this looks like it's merely the inverse | |\n| | | of *run*, that's because it is. Use | |\n| | | whichever suits your pipeline better, or | |\n| | | combine *run* and *skip* in the same | |\n| | | pipeline to toggle at runtime which steps | |\n| | | you want to execute. | |\n+---------------+----------+---------------------------------------------+----------------+\n| swallow | bool | If True, ignore any errors raised by the | False |\n| | | step and continue to the next step. | |\n| | | pypyr logs the error, so you'll know what | |\n| | | happened, but processing continues. | |\n+---------------+----------+---------------------------------------------+----------------+\n| while | dict | Repeat step until *stop* is True, or until | None |\n| | | *max* iterations reached. You have to | |\n| | | specify either *max* or *stop*. The loop | |\n| | | position counter is | |\n| | | ``context['whileCounter']`` | |\n| | | | |\n| | | If you specify both *max* and *stop*, the | |\n| | | loop exits when *stop* is True as long as | |\n| | | it's still under *max* iterations. *max* | |\n| | | will exit the loop even if *stop* is still | |\n| | | False. If you want to error and stop | |\n| | | processing when *max* exhausts (maybe you | |\n| | | are waiting for *stop* to reach True but | |\n| | | want to timeout after *max*) set | |\n| | | *errorOnMax* to True. | |\n+---------------+----------+---------------------------------------------+----------------+\n\nAll step decorators support `Substitutions`_. You can use `py strings`_ for\ndynamic boolean conditions like ``len(key) > 0``.\n\nIf no looping decorators are specified, the step will execute once (depending\non the conditional decorators' settings).\n\nIf all of this sounds complicated, don't panic! If you don't bother with any of\nthese the step will just run once by default.\n\ndecorator bool evaluation\n^^^^^^^^^^^^^^^^^^^^^^^^^\nNote that for all bool values, the standard Python truth value testing rules apply.\nhttps://docs.python.org/3/library/stdtypes.html#truth-value-testing\n\nSimply put, this means that 1, TRUE, True and true will be True.\n\nNone/Empty, 0,'', [], {} will be False.\n\nDecorator order of precedence\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nDecorators can interplay, meaning that the sequence of evaluation is important.\n\n- *run* or *skip* controls whether a step should execute on any\n given loop iteration, without affecting continued loop iteration.\n\n- *run* could be True but *skip* True will still skip the step.\n\n- A step can run multiple times in a *foreach* loop for each iteration of a\n *while* loop.\n\n- *swallow* can evaluate dynamically inside a loop to decide whether to swallow\n an error or not on a particular iteration.\n\n- *swallow* can swallow an error after *retry* exhausted max attempts.\n\n.. code-block:: yaml\n\n in # in evals once and only once at the beginning of step\n -> while # everything below loops inside while\n -> foreach # everything below loops inside foreach\n -> run # evals dynamically on each loop iteration\n -> skip # evals dynamically on each loop iteration after run\n -> retry # repeats step execution until no error\n [>>>actual step execution here<<<]\n -> swallow # evaluated dynamically on each loop iteration\n\nDecorator examples\n^^^^^^^^^^^^^^^^^^\n+------------------------------------------------+-----------------------------+\n| **example** | **link** |\n+------------------------------------------------+-----------------------------+\n| conditional step decorators | |step-decorators| |\n+------------------------------------------------+-----------------------------+\n| dynamic expression evaluation | |pystring-decorator| |\n+------------------------------------------------+-----------------------------+\n| foreach looping | |foreach-decorator| |\n+------------------------------------------------+-----------------------------+\n| foreach with dynamic conditional decorator | |foreach-dynamic| |\n| evaluation. | |\n+------------------------------------------------+-----------------------------+\n| retry | |retry-decorator| |\n+------------------------------------------------+-----------------------------+\n| retry with retryOn | |retry-decorator-retryon| |\n+------------------------------------------------+-----------------------------+\n| retry with stopOn | |retry-decorator-stopon| |\n+------------------------------------------------+-----------------------------+\n| while looping | |while-decorator| |\n+------------------------------------------------+-----------------------------+\n| while with sleep intervals | |while-sleep| |\n+------------------------------------------------+-----------------------------+\n| while combined with foreach | |while-foreach| |\n+------------------------------------------------+-----------------------------+\n| while with error on reaching max or never | |while-exhaust| |\n| reaching a stop condition. | |\n+------------------------------------------------+-----------------------------+\n| while loop that runs infinitely | |while-infinite| |\n+------------------------------------------------+-----------------------------+\n\n.. |step-decorators| replace:: `step decorators `__\n\n.. |pystring-decorator| replace:: `dynamic expression `__\n\n.. |foreach-decorator| replace:: `foreach `__\n\n.. |foreach-dynamic| replace:: `foreach dynamic conditionals `__\n\n.. |retry-decorator| replace:: `retry decorator `__\n\n.. |retry-decorator-retryon| replace:: `retry decorator retryOn `__\n\n.. |retry-decorator-stopon| replace:: `retry decorator stopOn `__\n\n.. |while-decorator| replace:: `while decorator `__\n\n.. |while-sleep| replace:: `while with sleep `__\n\n.. |while-foreach| replace:: `while foreach `__\n\n.. |while-exhaust| replace:: `while exhaust `__\n\n.. |while-infinite| replace:: `while infinite `__\n\nBuilt-in steps\n--------------\n\n+-------------------------------+-------------------------------------------------+------------------------------+\n| **step** | **description** | **input context properties** |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.assert`_ | Stop pipeline if item in context is not as | assert (dict) |\n| | expected. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.call`_ | Call another step-group. Continue with current | call (dict or str) |\n| | execution after the called groups are done. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.cmd`_ | Runs the program and args specified in the | cmd (string or dict) |\n| | context value ``cmd`` as a subprocess. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.contextclear`_ | Remove specified items from context. | contextClear (list) |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.contextclearall`_| Wipe the entire context. | |\n| | | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.contextmerge`_ | Merges values into context, preserving the | contextMerge (dict) |\n| | existing context hierarchy. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.contextset`_ | Set context values from already existing | contextSet (dict) |\n| | context values. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.contextsetf`_ | Set context keys from formatting | contextSetf (dict) |\n| | expressions with {token} substitutions. | |\n| | | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.debug`_ | Pretty print pypyr context to output. | debug (dict) |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.default`_ | Set default values in context. Only set values | defaults (dict) |\n| | if they do not exist already. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.echo`_ | Echo the context value ``echoMe`` to the output.| echoMe (string) |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.env`_ | Get, set or unset $ENVs. | env (dict) |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.envget`_ | Get $ENVs and use a default if they don't exist.| envget (list) |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.fetchjson`_ | Loads json file into pypyr context. | fetchJson (dict) |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.fetchyaml`_ | Loads yaml file into pypyr context. | fetchYaml (dict) |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.fileformat`_ | Parse file and substitute {tokens} from | fileFormat (dict) |\n| | context. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.fileformatjson`_ | Parse json file and substitute {tokens} from | fileFormatJson (dict) |\n| | context. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.fileformatyaml`_ | Parse yaml file and substitute {tokens} from | fileFormatYaml (dict) |\n| | context. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.filereplace`_ | Parse input file and replace search strings. | fileReplace (dict) |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.filewritejson`_ | Write payload to file in json format. | fileWriteJson (dict) |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.filewriteyaml`_ | Write payload to file in yaml format. | fileWriteYaml (dict) |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.glob`_ | Get paths from glob expression. | glob (string or list) |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.jump`_ | Jump to another step-group. This means the rest | jump (dict or str) |\n| | of the current step-group doesn't run. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.pathcheck`_ | Check if path exists on filesystem. | pathCheck (string or dict) |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.py`_ | Executes the context value ``pycode`` as python | pycode (string) |\n| | code. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.pype`_ | Run another pipeline from within the current | pype (dict) |\n| | pipeline. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.pypyrversion`_ | Writes installed pypyr version to output. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.now`_ | Saves current local date/time to context | nowIn (str) |\n| | ``now``. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.nowutc`_ | Saves current utc date/time to context | nowUtcIn (str) |\n| | ``nowUtc``. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.safeshell`_ | Alias for `pypyr.steps.cmd`_. | cmd (string or dict) |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.shell`_ | Runs the context value ``cmd`` in the default | cmd (string or dict) |\n| | shell. Use for pipes, wildcards, $ENVs, ~ | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.stop`_ | Stop pypyr entirely. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.stoppipeline`_ | Stop current pipeline. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.stopstepgroup`_ | Stop current step-group. | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n| `pypyr.steps.tar`_ | Archive and/or extract tars with or without | tar (dict) |\n| | compression. Supports gzip, bzip2, lzma. | |\n| | | |\n+-------------------------------+-------------------------------------------------+------------------------------+\n\npypyr.steps.assert\n^^^^^^^^^^^^^^^^^^\nAssert that something is True or equal to something else.\n\nUses these context keys:\n\n- ``assert``\n\n - ``this``\n\n - mandatory\n - If assert['equals'] not specified, evaluates as a boolean.\n\n - ``equals``\n\n - optional\n - If specified, compares ``assert['this']`` to ``assert['equals']``\n\nIf ``assert['this']`` evaluates to False raises error.\n\nIf ``assert['equals']`` is specified, raises error if\n``assert['this'] != assert['equals']``.\n\nSupports `Substitutions`_.\n\nExamples:\n\n.. code-block:: yaml\n\n assert: # continue pipeline\n this: True\n assert: # stop pipeline\n this: False\n\nor with substitutions:\n\n.. code-block:: yaml\n\n interestingValue: True\n assert:\n this: '{interestingValue}' # continue with pipeline\n\nNon-0 numbers evalute to True:\n\n.. code-block:: yaml\n\n assert:\n this: 1 # non-0 numbers assert to True. continue with pipeline\n\nString equality:\n\n.. code-block:: yaml\n\n assert:\n this: 'up the valleys wild'\n equals: 'down the valleys wild' # strings not equal. stop pipeline.\n\nString equality with substitutions:\n\n.. code-block:: yaml\n\n k1: 'down'\n k2: 'down'\n assert:\n this: '{k1} the valleys wild'\n equals: '{k2} the valleys wild' # substituted strings equal. continue pipeline.\n\n\nNumber equality:\n\n.. code-block:: yaml\n\n assert:\n this: 123.45\n equals: 0123.450 # numbers equal. continue with pipeline.\n\nNumber equality with substitutions:\n\n.. code-block:: yaml\n\n numberOne: 123.45\n numberTwo: 678.9\n assert:\n this: '{numberOne}'\n equals: '{numberTwo}' # substituted numbers not equal. Stop pipeline.\n\nComplex types:\n\n.. code-block:: yaml\n\n complexOne:\n - thing1\n - k1: value1\n k2: value2\n k3:\n - sub list 1\n - sub list 2\n complexTwo:\n - thing1\n - k1: value1\n k2: value2\n k3:\n - sub list 1\n - sub list 2\n assert:\n this: '{complexOne}'\n equals: '{complexTwo}' # substituted types equal. Continue pipeline.\n\n\nSee a worked example `for assert here\n`__.\n\npypyr.steps.call\n^^^^^^^^^^^^^^^^\nCall another step-group. Once the called group(s) are complete, continues\nprocessing from the point where you called.\n\nIf you want to jump to a different step-group and ignore the rest of the\nstep-group you're in, use `pypyr.steps.jump`_ instead.\n\n*call* expects a context item *call*. It can take one of two forms:\n\n.. code-block:: yaml\n\n - name: pypyr.steps.call\n comment: simple string means just call the step-group named \"callme\"\n in:\n call: callme\n - name: pypyr.steps.call\n comment: specify groups, success and failure.\n in:\n call:\n groups: ['callme', 'noreally'] # list. Step-groups to call.\n success: group_to_call_on_success # string. Single step-group name.\n failure: group_to_call_on_failure # string. Single step-group name.\n\n*call.groups* can be a simple string if you're just calling a single group -\ni.e you don't need to make it a list of one item.\n\nCall can be handy if you use it in conjunction with looping step decorators\nlike *while* or *foreach*:\n\n.. code-block:: yaml\n\n steps:\n - name: pypyr.steps.echo\n in:\n echoMe: this is the 1st step of steps\n - name: pypyr.steps.call\n in:\n call: arbgroup\n - name: pypyr.steps.echo\n in:\n echoMe: You'll see me AFTER arbgroup is done.\n - name: pypyr.steps.call\n foreach: ['one', 'two', 'three']\n in:\n call: repeatme\n arbgroup:\n - name: pypyr.steps.echo\n in:\n echoMe: this is arb group\n - pypyr.steps.stopstepgroup\n - name: pypyr.steps.echo\n in:\n echoMe: if you see me something is WRONG.\n repeatme:\n - name: pypyr.steps.echo\n in:\n echoMe: this is iteration {i}\n\n\nThis will result in:\n\n.. code-block:: text\n\n NOTIFY:pypyr.steps.echo:run_step: this is the 1st step of steps\n NOTIFY:pypyr.steps.echo:run_step: this is arb group\n NOTIFY:pypyr.steps.echo:run_step: You'll see me AFTER arbgroup is done.\n NOTIFY:pypyr.steps.echo:run_step: this is iteration one\n NOTIFY:pypyr.steps.echo:run_step: this is iteration two\n NOTIFY:pypyr.steps.echo:run_step: this is iteration three\n\n\nCall only runs success or failure groups if you actually specify these.\n\nAll inputs support string `Substitutions`_.\n\nSee a worked example for `call here\n`__.\n\npypyr.steps.cmd\n^^^^^^^^^^^^^^^\nRuns the context value *cmd* as a sub-process.\n\nIn *cmd*, you cannot use things like exit, return, shell pipes, filename\nwildcards, environment variable expansion, and expansion of ~ to a user\u2019s\nhome directory. Use `pypyr.steps.shell`_ for this instead. *cmd* runs a\nprogram, it does not invoke the shell.\n\nInput context can take one of two forms:\n\n.. code-block:: yaml\n\n - name: pypyr.steps.cmd\n description: passing cmd as a string does not save the output to context.\n it prints stdout in real-time.\n in:\n cmd: 'echo ${PWD}'\n - name: pypyr.steps.cmd\n description: passing cmd as a dict allows you to specify if you want to\n save the output to context.\n it prints command output only AFTER it has finished running.\n in:\n cmd:\n run: 'echo ${PWD}'\n save: True\n cwd: './current/working/dir/here'\n\nIf ``cwd`` is specified, will change the current working directory to *cwd* to\nexecute this command. The directory change is only for the duration of this\nstep, not any subsequent steps. If *cwd* is specified, the executable or program\nspecified in *run* is relative to the *cwd* if the *run* cmd uses relative paths.\n\nIf ``cwd`` is not specified, defaults to the current working directory, which\nis from wherever you are running ``pypyr``.\n\nBe aware that if *save* is True, all of the command output ends up in memory.\nDon't specify it unless your pipeline uses the stdout/stderr response in\nsubsequent steps. Keep in mind that if the invoked command return code returns\na non-zero return code pypyr will automatically raise a *CalledProcessError*\nand stop the pipeline.\n\nIf *save* is True, pypyr will save the output to context as follows:\n\n.. code-block:: yaml\n\n cmdOut:\n returncode: 0\n stdout: 'stdout str here. None if empty.'\n stderr: 'stderr str here. None if empty.'\n\n*cmdOut.returncode* is the exit status of the called process. Typically 0 means\nOK. A negative value -N indicates that the child was terminated by signal N\n(POSIX only).\n\nYou can use cmdOut in subsequent steps like this:\n\n.. code-block:: yaml\n\n - name: pypyr.steps.echo\n run: !py \"cmdOut['returncode'] == 0\"\n in:\n echoMe: \"you'll only see me if cmd ran successfully with return code 0.\n the command output was: {cmdOut[stdout]}\"\n\nSupports string `Substitutions`_.\n\nExample pipeline yaml:\n\n.. code-block:: bash\n\n steps:\n - name: pypyr.steps.cmd\n in:\n cmd: ls -a\n\nSee a worked example `for cmd here\n`__.\n\npypyr.steps.contextclear\n^^^^^^^^^^^^^^^^^^^^^^^^\nRemove the specified items from the context.\n\nWill iterate ``contextClear`` and remove those keys from context.\n\nFor example, say input context is:\n\n.. code-block:: yaml\n\n key1: value1\n key2: value2\n key3: value3\n key4: value4\n contextClear:\n - key2\n - key4\n - contextClear\n\nThis will result in return context:\n\n.. code-block:: yaml\n\n key1: value1\n key3: value3\n\nNotice how contextClear also cleared itself in this example.\n\npypyr.steps.contextclearall\n^^^^^^^^^^^^^^^^^^^^^^^^^^^\nWipe the entire context. No input context arguments required.\n\nYou can always use *contextclearall* as a simple step. Sample pipeline yaml:\n\n.. code-block:: yaml\n\n steps:\n - my.arb.step\n - pypyr.steps.contextclearall\n - another.arb.step\n\n\npypyr.steps.contextmerge\n^^^^^^^^^^^^^^^^^^^^^^^^\nMerges values into context, preserving the existing hierarchy while only\nupdating the differing values as specified in the contextmerge input.\n\nBy comparison, *contextset* and *contextsetf* overwrite the destination\nhierarchy that is in context already,\n\nThis step merges the contents of the context key *contextMerge* into context.\nThe contents of the *contextMerge* key must be a dictionary.\n\nFor example, say input context is:\n\n.. code-block:: yaml\n\n key1: value1\n key2: value2\n key3:\n k31: value31\n k32: value32\n contextMerge:\n key2: 'aaa_{key1}_zzz'\n key3:\n k33: value33_{key1}\n key4: 'bbb_{key2}_yyy'\n\nThis will result in return context:\n\n.. code-block:: yaml\n\n key1: value1\n key2: aaa_value1_zzz\n key3:\n k31: value31\n k32: value32\n k33: value33_value1\n key4: bbb_aaa_value1_zzz_yyy\n\nList, Set and Tuple merging is purely additive, with no checks for uniqueness\nor already existing list items. E.g context `[0,1,2]` with\ncontextMerge `[2,3,4]` will result in `[0,1,2,2,3,4]`.\n\nKeep this in mind especially where complex types like dicts nest inside a list\n- a merge will always add a new dict list item, not merge it into whatever dicts\nmight exist on the list already.\n\nSee a worked example for `contextmerge here\n`__.\n\npypyr.steps.contextset\n^^^^^^^^^^^^^^^^^^^^^^\nSets context values from already existing context values.\n\nThis is handy if you need to prepare certain keys in context where a next step\nmight need a specific key. If you already have the value in context, you can\ncreate a new key (or update existing key) with that value.\n\n*contextset* and *contextsetf* overwrite existing keys. If you want to merge\nnew values into an existing destination hierarchy, use\n`pypyr.steps.contextmerge`_ instead.\n\nSo let's say you already have `context['currentKey'] = 'eggs'`.\nIf you run newKey: currentKey, you'll end up with `context['newKey'] == 'eggs'`\n\nFor example, say your context looks like this,\n\n.. code-block:: yaml\n\n key1: value1\n key2: value2\n key3: value3\n\nand your pipeline yaml looks like this:\n\n.. code-block:: yaml\n\n steps:\n - name: pypyr.steps.contextset\n in:\n contextSet:\n key2: key1\n key4: key3\n\nThis will result in context like this:\n\n.. code-block:: yaml\n\n key1: value1\n key2: value1\n key3: value3\n key4: value3\n\nSee a worked example `for contextset here\n`__.\n\npypyr.steps.contextsetf\n^^^^^^^^^^^^^^^^^^^^^^^\nSet context keys from formatting expressions with `Substitutions`_.\n\nRequires the following context:\n\n.. code-block:: yaml\n\n contextSetf:\n newkey: '{format expression}'\n newkey2: '{format expression}'\n\n*contextset* and *contextsetf* overwrite existing keys. If you want to merge\nnew values into an existing destination hierarchy, use\n`pypyr.steps.contextmerge`_ instead.\n\nFor example, say your context looks like this:\n\n.. code-block:: yaml\n\n key1: value1\n key2: value2\n answer: 42\n\nand your pipeline yaml looks like this:\n\n.. code-block:: yaml\n\n steps:\n - name: pypyr.steps.contextsetf\n in:\n contextSetf:\n key2: any old value without a substitution - it will be a string now.\n key4: 'What do you get when you multiply six by nine? {answer}'\n\nThis will result in context like this:\n\n.. code-block:: yaml\n\n key1: value1\n key2: any old value without a substitution - it will be a string now.\n answer: 42\n key4: 'What do you get when you multiply six by nine? 42'\n\nYou can use *contextsetf* in conjunction with `py strings`_ for conditional\nassignment of context items or ternary expressions.\n\n.. code-block:: yaml\n\n arb1: null\n arb2: ''\n arb3: eggy\n arb4: [1,1,2,3,5,8]\n contextSetf:\n isNull: !py arb1 is None # make a bool based on None\n isEmpty: !py bool(arb2) # use truthy, empty strings are false\n ternaryResult: !py \"'eggs' if arb3 == 'eggy' else 'ham'\"\n isIn: !py 10 in arb4 # bool if thing in list\n\nSee a worked example `for contextsetf here\n`__.\n\npypyr.steps.debug\n^^^^^^^^^^^^^^^^^\nPretty print the context to output.\n\nPrint the pypyr context to the pypyr output. This is likely to be the console.\nThis may assist in debugging when trying to see what values are what.\n\ndebug prints to the INFO (20) log-level. This means you won't see debug output\nunless you specify ``pypyr mypype --log 20`` or lower.\n\nObviously, be aware that if you have sensitive values like passwords in your\ncontext you probably want to be careful about this. No duh.\n\nAll inputs are optional. This means you can run debug in a pipeline as a\nsimple step just with\n\n.. code-block:: yaml\n\n steps:\n - name: my.arb.step\n in:\n arb: arb1\n - pypyr.steps.debug # use debug as a simple step, with no config\n - name: another.arb.step\n in:\n another: value\n\nIn this case it will dump the entire context as is without applying formatting.\n\nDebug supports the following optional inputs:\n\n.. code-block:: yaml\n\n debug: # optional\n keys: keytodump # optional. str for a single key name to print.\n # or a list of key names to print ['key1', 'key2'].\n # if not specified, print entire context.\n format: False # optional. Boolean, defaults False.\n # Applies formatting expressions to output.\n\nSee some worked examples of `use debug to pretty print context here\n`__.\n\npypyr.steps.default\n^^^^^^^^^^^^^^^^^^^\nSets values in context if they do not exist already. Does not overwrite\nexisting values. Supports nested hierarchies.\n\nThis is especially useful for setting default values in context, for example\nwhen using `optional arguments\n`__.\nfrom the shell.\n\nThis step sets the contents of the context key *defaults* into context where\nkeys in *defaults* do not exist in context already.\nThe contents of the *defaults* key must be a dictionary.\n\nExample:\nGiven a context like this:\n\n.. code-block:: yaml\n\n key1: value1\n key2:\n key2.1: value2.1\n key3: None\n\nAnd *defaults* input like this:\n\n.. code-block:: yaml\n\n key1: updated value here won't overwrite since it already exists\n key2:\n key2.2: value2.2\n key3: key 3 exists so I won't overwrite\n\nWill result in context:\n\n.. code-block:: yaml\n\n key1: value1\n key2:\n key2.1: value2.1\n key2.2: value2.2\n key3: None\n\nBy comparison, the *in* step decorator, and the steps *contextset*,\n*contextsetf* and *contextmerge* overwrite values that are in context already.\n\nThe recursive if-not-exists-then-set check happens for dictionaries, but not\nfor items in Lists, Sets and Tuples. You can set default values of type List,\nSet or Tuple if their keys don't exist in context already, but this step will\nnot recurse through the List, Set or Tuple itself.\n\nSupports `Substitutions`_. String interpolation applies to keys and values.\n\nSee a worked example for `default here\n`__.\n\npypyr.steps.echo\n^^^^^^^^^^^^^^^^\nEcho the context value ``echoMe`` to the output.\n\nFor example, if you had pipelines/mypipeline.yaml like this:\n\n.. code-block:: yaml\n\n context_parser: pypyr.parser.keyvaluepairs\n steps:\n - name: pypyr.steps.echo\n\nYou can run:\n\n.. code-block:: bash\n\n pypyr mypipeline \"echoMe=Ceci n'est pas une pipe\"\n\n\nAlternatively, if you had pipelines/look-ma-no-params.yaml like this:\n\n.. code-block:: yaml\n\n steps:\n - name: pypyr.steps.echo\n description: Output echoMe\n in:\n echoMe: Ceci n'est pas une pipe\n\n\nYou can run:\n\n.. code-block:: bash\n\n $ pypyr look-ma-no-params\n\nSupports `Substitutions`_.\n\npypyr.steps.env\n^^^^^^^^^^^^^^^\nGet, set or unset environment variables.\n\nThe ``env`` context key must exist. ``env`` can contain a combination of get,\nset and unset keys.\nYou must specify at least one of ``get``, ``set`` and ``unset``.\n\n.. code-block:: yaml\n\n env:\n get:\n contextkey1: env1\n contextkey2: env2\n set:\n env1: value1\n env2: value2\n unset:\n - env1\n - env2\n\nThis step will run whatever combination of Get, Set and Unset you specify.\nRegardless of combination, execution order is Get, Set, Unset.\n\nSee a worked example `for environment variables here\n`__.\n\n\nenv get\n\"\"\"\"\"\"\"\nGet $ENVs into the pypyr context.\n\nIf the $ENV does not exist, this step will raise an error. If you want to get\nan $ENV that might not exist without throwing an error, use\n`pypyr.steps.envget`_ instead.\n\n``context['env']['get']`` must exist. It's a dictionary.\n\nValues are the names of the $ENVs to write to the pypyr context.\n\nKeys are the pypyr context item to which to write the $ENV values.\n\nFor example, say input context is:\n\n.. code-block:: yaml\n\n key1: value1\n key2: value2\n pypyrCurrentDir: value3\n env:\n get:\n pypyrUser: USER\n pypyrCurrentDir: PWD\n\n\nThis will result in context:\n\n.. code-block:: yaml\n\n key1: value1\n key2: value2\n key3: value3\n pypyrCurrentDir: <>\n pypyrUser: <>\n\nenv set\n\"\"\"\"\"\"\"\nSet $ENVs from the pypyr context.\n\n``context['env']['set']`` must exist. It's a dictionary.\n\nValues are strings to write to $ENV. You can use {key} `Substitutions`_ to\nformat the string from context.\nKeys are the names of the $ENV values to which to write.\n\nFor example, say input context is:\n\n.. code-block:: yaml\n\n key1: value1\n key2: value2\n key3: value3\n env:\n set:\n MYVAR1: {key1}\n MYVAR2: before_{key3}_after\n MYVAR3: arbtexthere\n\nThis will result in the following $ENVs:\n\n.. code-block:: yaml\n\n $MYVAR1 == value1\n $MYVAR2 == before_value3_after\n $MYVAR3 == arbtexthere\n\nNote that the $ENVs are not persisted system-wide, they only exist for the\npypyr sub-processes, and as such for the subsequent steps during this pypyr\npipeline execution. If you set an $ENV here, don't expect to see it in your\nsystem environment variables after the pipeline finishes running.\n\nenv unset\n\"\"\"\"\"\"\"\"\"\nUnset $ENVs.\n\nContext is a dictionary or dictionary-like. context is mandatory.\n\n``context['env']['unset']`` must exist. It's a list.\nList items are the names of the $ENV values to unset.\n\nFor example, say input context is:\n\n.. code-block:: yaml\n\n key1: value1\n key2: value2\n key3: value3\n env:\n unset:\n - MYVAR1\n - MYVAR2\n\nThis will result in the following $ENVs being unset:\n\n.. code-block:: bash\n\n $MYVAR1\n $MYVAR2\n\npypyr.steps.envget\n^^^^^^^^^^^^^^^^^^\nGet environment variables, and assign a default value to context if they do\nnot exist.\n\nThe difference between *pypyr.steps.envget* and *pypyr.steps.env* `env get`_,\nis that *pypyr.steps.envget* won't raise an error if the $ENV doesn't exist.\n\nThe ``envget`` context key must exist.\n\n.. code-block:: yaml\n\n - name: pypyr.steps.envget\n description: if env MACAVITY is not there, set context theHiddenPaw to default.\n in:\n envGet:\n env: MACAVITY\n key: theHiddenPaw\n default: but macavity wasn't there!\n\n\nIf you need to get more than one $ENV, you can pass a list to ``envget``.\n\n.. code-block:: yaml\n\n envGet:\n # get >1 $ENVs by passing them in as list items\n - env: ENV_NAME1 # mandatory\n key: saveMeHere1 # mandatory\n default: null # optional\n - env: ENV_NAME2\n key: saveMeHere2\n default: 'use-me-if-env-not-there' # optional\n\n\n- ``env``: Mandatory. This is the environment variable name. This is the bare\n environment variable name, do not put the $ in front of it.\n- ``key``: Mandatory. The pypyr context key destination to which to copy the\n $ENV value.\n- ``default`` Optional. Assign this value to ``key`` if the $ENV specified\n by ``env`` doesn't exist.\n\n - If you want to create a key in the pypyr context with an empty value,\n specify ``null``.\n - If you do NOT want to create a key in the pypyr context, do not have a\n default input.\n\n.. code-block:: yaml\n\n # save ENV_NAME to key. If ENV_NAME doesn't exist, do NOT set saveMeHere.\n envGet:\n - env: ENV_NAME\n key: saveMeHere # saveMeHere won't be in context if ENV_NAME not there.\n # this is because the default keyword is not specified.\n\nAll inputs support `Substitutions`_.\n\nSee a worked example for `getting environment variables with defaults here\n`__.\n\n\npypyr.steps.fetchjson\n^^^^^^^^^^^^^^^^^^^^^\nLoads a json file into the pypyr context.\n\nThis step requires the following key in the pypyr context to succeed:\n\n.. code-block:: yaml\n\n fetchJson:\n path: ./path.json # required. path to file on disk. can be relative.\n key: 'destinationKey' # optional. write json to this context key.\n\nIf ``key`` is not specified, json writes directly to context root.\n\nIf you do not want to specify a key, you can also use the streamlined format:\n\n.. code-block:: yaml\n\n fetchJson: ./path.json # required. path to file on disk. can be relative.\n\nAll inputs support `Substitutions`_.\n\nJson parsed from the file will be merged into the pypyr context. This will\noverwrite existing values if the same keys are already in there.\n\nI.e if file json has ``{'eggs' : 'boiled'}``, but context ``{'eggs': 'fried'}``\nalready exists, returned ``context['eggs']`` will be 'boiled'.\n\nIf ``key`` is not specified, the json should not be an array [] at the\nroot level, but rather an Object {}.\n\nSee some worked examples of `fetchjson here\n`__.\n\npypyr.steps.fetchyaml\n^^^^^^^^^^^^^^^^^^^^^\nLoads a yaml file into the pypyr context.\n\nThis step requires the following key in the pypyr context to succeed:\n\n.. code-block:: yaml\n\n fetchYaml:\n path: ./path.yaml # required. path to file on disk. can be relative.\n key: 'destinationKey' # optional. write yaml to this context key.\n\nIf ``key`` not specified, yaml writes directly to context root.\n\nIf you do not want to specify a key, you can also use the streamlined format:\n\n.. code-block:: yaml\n\n fetchYaml: ./path.yaml # required. path to file on disk. can be relative.\n\nAll inputs support `Substitutions`_.\n\nYaml parsed from the file will be merged into the pypyr context. This will\noverwrite existing values if the same keys are already in there.\n\nI.e if file yaml has\n\n.. code-block:: yaml\n\n eggs: boiled\n\nbut context ``{'eggs': 'fried'}`` already exists, returned ``context['eggs']``\nwill be 'boiled'.\n\nIf ``key`` is not specified, the yaml should not be a list at the top\nlevel, but rather a mapping.\n\nSo the top-level yaml should not look like this:\n\n.. code-block:: yaml\n\n - eggs\n - ham\n\nbut rather like this:\n\n.. code-block:: yaml\n\n breakfastOfChampions:\n - eggs\n - ham\n\nSee some worked examples of `fetchyaml here\n`__.\n\npypyr.steps.fileformat\n^^^^^^^^^^^^^^^^^^^^^^\nParses input text file and substitutes {tokens} in the text of the file\nfrom the pypyr context.\n\nThe following context keys expected:\n\n- fileFormat\n\n - in\n\n - Mandatory path(s) to source file on disk.\n - This can be a string path to a single file, or a glob, or a list of paths\n and globs. Each path can be a relative or absolute path.\n\n - out\n\n - Write output file to here. Will create directories in path if these do not\n exist already.\n - *out* is optional. If not specified, will edit the *in* files in-place.\n - If in-path refers to >1 file (e.g it's a glob or list), out path can only\n be a directory - it doesn't make sense to write >1 file to the same\n single file output (this is not an appender.)\n - To ensure out_path is read as a directory and not a file, be sure to have\n the os' path separator (/ on a sane filesystem) at the end.\n - Files are created in the *out* directory with the same name they had in\n *in*.\n\nSo if you had a text file like this:\n\n.. code-block:: text\n\n {k1} sit thee down and write\n In a book that all may {k2}\n\nAnd your pypyr context were:\n\n.. code-block:: yaml\n\n k1: pypyr\n k2: read\n\nYou would end up with an output file like this:\n\n.. code-block:: text\n\n pypyr sit thee down and write\n In a book that all may read\n\nExample with globs and a list. You can also pass a single string glob, it\ndoesn't need to be in a list.\n\n.. code-block:: yaml\n\n fileFormat:\n in:\n # ** recurses sub-dirs per usual globbing\n - ./testfiles/sub3/**/*.txt\n - ./testfiles/??b/fileformat-in.*.txt\n # note the dir separator at the end.\n # since >1 in files, out can only be a dir.\n out: ./out/replace/\n\nIf you do not specify *out*, it will over-write (i.e edit) all the files\nspecified by *in*.\n\n.. code-block:: yaml\n\n fileFormat:\n # in-place edit/overwrite all the files in. this can also be a glob, or\n # a mixed list of paths and/or globs.\n in: ./infile.txt\n\nThe file in and out paths support `Substitutions`_.\n\nSee a worked example of\n`fileformat here\n`_.\n\npypyr.steps.fileformatjson\n^^^^^^^^^^^^^^^^^^^^^^^^^^\nParses input json file and substitutes {tokens} from the pypyr context.\n\nPretty much does the same thing as `pypyr.steps.fileformat`_, only it makes it\neasier to work with curly braces for substitutions without tripping over the\njson's structural braces.\n\nThe following context keys expected:\n\n- fileFormatJson\n\n - in\n\n - Mandatory path(s) to source file on disk.\n - This can be a string path to a single file, or a glob, or a list of paths\n and globs. Each path can be a relative or absolute path.\n\n - out\n\n - Write output file to here. Will create directories in path if these do not\n exist already.\n - *out* is optional. If not specified, will edit the *in* files in-place.\n - If in-path refers to >1 file (e.g it's a glob or list), out path can only\n be a directory - it doesn't make sense to write >1 file to the same\n single file output (this is not an appender.)\n - To ensure out_path is read as a directory and not a file, be sure to have\n the os' path separator (/ on a sane filesystem) at the end.\n - Files are created in the *out* directory with the same name they had in\n *in*.\n\nSee `pypyr.steps.fileformat`_ for more examples on in/out path handling - the\nsame processing rules apply.\n\nExample with a glob input:\n\n.. code-block:: yaml\n\n fileFormatJson:\n in: ./testfiles/sub3/**/*.txt\n # note the dir separator at the end.\n # since >1 in files, out can only be a dir.\n out: ./out/replace/\n\nIf you do not specify *out*, it will over-write (i.e edit) all the files\nspecified by *in*.\n\n`Substitutions`_ enabled for keys and values in the source json.\n\nThe file in and out paths also support `Substitutions`_.\n\nSee a worked example of\n`fileformatjson here\n`_.\n\npypyr.steps.fileformatyaml\n^^^^^^^^^^^^^^^^^^^^^^^^^^\nParses input yaml file and substitutes {tokens} from the pypyr context.\n\nPretty much does the same thing as `pypyr.steps.fileformat`_, only it makes it\neasier to work with curly braces for substitutions without tripping over the\nyaml's structural braces. If your yaml doesn't use curly braces that aren't\nmeant for {token} substitutions, you can happily use `pypyr.steps.fileformat`_\ninstead - it's more memory efficient.\n\nThis step does not preserve comments. Use `pypyr.steps.fileformat`_ if you need\nto preserve comments on output.\n\nThe following context keys expected:\n\n- fileFormatYaml\n\n - in\n\n - Mandatory path(s) to source file on disk.\n - This can be a string path to a single file, or a glob, or a list of paths\n and globs. Each path can be a relative or absolute path.\n\n - out\n\n - Write output file to here. Will create directories in path if these do not\n exist already.\n - *out* is optional. If not specified, will edit the *in* files in-place.\n - If in-path refers to >1 file (e.g it's a glob or list), out path can only\n be a directory - it doesn't make sense to write >1 file to the same\n single file output (this is not an appender.)\n - To ensure out_path is read as a directory and not a file, be sure to have\n the os' path separator (/ on a sane filesystem) at the end.\n - Files are created in the *out* directory with the same name they had in\n *in*.\n\nSee `pypyr.steps.fileformat`_ for more examples on in/out path handling - the\nsame processing rules apply.\n\nExample with a glob input and a normal path in a list:\n\n.. code-block:: yaml\n\n fileFormatYaml:\n in: [./file1.yaml, ./testfiles/sub3/**/*.yaml]\n # note the dir separator at the end.\n # since >1 in files, out can only be a dir.\n out: ./out/replace/\n\nIf you do not specify *out*, it will over-write (i.e edit) all the files\nspecified by *in*.\n\nThe file in and out paths support `Substitutions`_.\n\nSee a worked example of\n`fileformatyaml\n`_.\n\npypyr.steps.filereplace\n^^^^^^^^^^^^^^^^^^^^^^^\nParses input text file and replaces a search string.\n\nThe other *fileformat* steps, by way of contradistinction, uses string\nformatting expressions inside {braces} to format values against the pypyr\ncontext. This step, however, let's you specify any search string and replace it\nwith any replace string. This is handy if you are in a file where curly braces\naren't helpful for a formatting expression - e.g inside a .js file.\n\nThe following context keys expected:\n\n- fileReplace\n\n - in\n\n - Mandatory path(s) to source file on disk.\n - This can be a string path to a single file, or a glob, or a list of paths\n and globs. Each path can be a relative or absolute path.\n\n - out\n\n - Write output file to here. Will create directories in path if these do not\n exist already.\n - *out* is optional. If not specified, will edit the *in* files in-place.\n - If in-path refers to >1 file (e.g it's a glob or list), out path can only\n be a directory - it doesn't make sense to write >1 file to the same\n single file output (this is not an appender.)\n - To ensure out_path is read as a directory and not a file, be sure to have\n the os' path separator (/ on a sane filesystem) at the end.\n - Files are created in the *out* directory with the same name they had in\n *in*.\n\n - replacePairs\n\n - dictionary where format is:\n\n - 'find_string': 'replace_string'\n\nExample input context:\n\n.. code-block:: yaml\n\n fileReplace:\n in: ./infile.txt\n out: ./outfile.txt\n replacePairs:\n findmestring: replacewithme\n findanotherstring: replacewithanotherstring\n alaststring: alastreplacement\n\n\nExample with globs and a list. You can also pass a single string glob.\n\n.. code-block:: yaml\n\n fileReplace:\n in:\n # ** recurses sub-dirs per usual globbing\n - ./testfiles/replace/sub/**\n - ./testfiles/replace/*.ext\n # note the dir separator at the end.\n # since >1 in files, out can only be a dir.\n out: ./out/replace/\n replacePairs:\n findmestring: replacewithme\n\nIf you do not specify *out*, it will over-write (i.e edit) all the files\nspecified by *in*.\n\n.. code-block:: yaml\n\n fileReplace:\n # in-place edit/overwrite all the files in\n in: ./infile.txt\n replacePairs:\n findmestring: replacewithme\n\nfileReplace also does string substitutions from context on the replacePairs. It\ndoes this before it search & replaces the *in* file.\n\nBe careful of order. The last string replacement expression could well replace\na replacement that an earlier replacement made in the sequence.\n\nIf replacePairs is not an ordered collection, replacements could evaluate in\nany given order. If you are creating your *in* parameters in the pipeline yaml,\ndon't worry about it, it will be an ordered dictionary already, so life is good.\n\nThe file in and out paths support `Substitutions`_.\n\nSee a worked\n`example here\n`_.\n\npypyr.steps.filewritejson\n^^^^^^^^^^^^^^^^^^^^^^^^^\nWrite a payload to a json file on disk.\n\n*filewritejson* expects the following input context:\n\n.. code-block:: yaml\n\n fileWriteJson:\n path: /path/to/output.json # destination file\n payload: # payload to write to path\n key1: value1 # output json will have\n key2: value2 # key1 and key2.\n\nIf you do not specify *payload*, pypyr will write the entire context to the\noutput file in json format. Be careful if you have sensitive values like\npasswords or private keys!\n\nAll inputs support `Substitutions`_. This means you can specify another context\nitem to be the path and/or the payload, for example:\n\n.. code-block:: yaml\n\n arbkey: arbvalue\n writehere: /path/to/output.json\n writeme:\n this: json content\n will: be written to\n thepath: with substitutions like this {arbkey}.\n fileWriteJson:\n path: '{writehere}'\n payload: '{writeme}'\n\nSubstitution processing runs on the output. In the above example, in the output\njson file created at */path/to/output.json*, the ``{arbkey}`` expression in\nthe last line will substitute like this:\n\n.. code-block:: json\n\n {\n \"this\": \"json content\",\n \"will\": \"be written to\",\n \"thepath\": \"with substitutions like this arbvalue.\"\n }\n\nSee a worked `filewritejson example here\n`_.\n\npypyr.steps.filewriteyaml\n^^^^^^^^^^^^^^^^^^^^^^^^^\nWrite a payload to a yaml file on disk.\n\n*filewriteyaml* expects the following input context:\n\n.. code-block:: yaml\n\n fileWriteYaml:\n path: /path/to/output.yaml # destination file\n payload: # payload to write to path\n key1: value1 # output yaml will have\n key2: value2 # key1 and key2.\n\nIf you do not specify *payload*, pypyr will write the entire context to the\noutput file in yaml format. Be careful if you have sensitive values like\npasswords or private keys!\n\nAll inputs support `Substitutions`_. This means you can specify another context\nitem to be the path and/or the payload, for example:\n\n.. code-block:: yaml\n\n arbkey: arbvalue\n writehere: /path/to/output.yaml\n writeme:\n this: yaml content\n will: be written to\n thepath: with substitutions like this {arbkey}.\n fileWriteYaml:\n path: '{writehere}'\n payload: '{writeme}'\n\nSubstitution processing runs on the output. In the above example, in the output\nyaml file created at */path/to/output.yaml*, the ``{arbkey}`` expression in\nthe last line will substitute like this:\n\n.. code-block:: yaml\n\n this: yaml content\n will: be written to\n thepath: with substitutions like this arbvalue.\n\nSee a worked `filewriteyaml example here\n`_.\n\npypyr.steps.glob\n^^^^^^^^^^^^^^^^\nResolves a glob and gets all the paths that exist on the filesystem for the\ninput glob.\n\nA path can point to a file or a directory.\n\nThe ``glob`` context key must exist.\n\n.. code-block:: yaml\n\n - name: pypyr.steps.glob\n in:\n glob: ./**/*.py # single glob\n\nIf you want to resolve multiple globs simultaneously and combine the results,\nyou can pass a list instead. You can freely mix literal paths and globs.\n\n.. code-block:: yaml\n\n - name: pypyr.steps.glob\n in:\n glob:\n - ./file1 # literal relative path\n - ./dirname # also finds dirs\n - ./**/{arbkey}* # glob with a string formatting expression\n\nAfter *glob* completes, the ``globOut`` context key is available.\nThis contains the results of the *glob* operation.\n\n.. code-block:: yaml\n\n globOut: # list of strings. Paths of all files found.\n ['file1', 'dir1', 'blah/arb']\n\nYou can use ``globOut`` as the list to enumerate in a ``foreach`` decorator\nstep, to run a step for each file found.\n\n.. code-block:: yaml\n\n - name: pypyr.steps.glob\n in:\n glob: ./get-files/**/*\n - name: pypyr.steps.pype\n foreach: '{globOut}'\n in:\n pype:\n name: pipeline-does-something-with-single-file\n\nAll inputs support `Substitutions`_. This means you can specify another context\nitem to be an individual path, or part of a path, or the entire path list.\n\nSee a worked\nexample for `glob here\n`_.\n\npypyr.steps.jump\n^^^^^^^^^^^^^^^^\nJump to another step-group. This effectively stops processing on the current\nstep-group you are jumping from.\n\nIf you want to return to the point of origin after the step-group you\njumped to completes, use `pypyr.steps.call`_ instead.\n\n*jump* expects a context item *jump*. It can take one of two forms:\n\n.. code-block:: yaml\n\n - name: pypyr.steps.jump\n comment: simple string means just call the step-group named \"jumphere\"\n in:\n jump: jumphere\n - name: pypyr.steps.call\n comment: specify groups, success and failure.\n in:\n jump:\n groups: ['jumphere', 'andhere'] # list. Step-group sequence to jump to.\n success: group_to_call_on_success # string. Single step-group name.\n failure: group_to_call_on_failure # string. Single step-group name.\n\n*jump.groups* can be a simple string if you're just jumping a single group -\ni.e you don't need to make it a list of one item.\n\nJump is handy when you want to transfer control from a current step-group to\na different sequence of steps. So you can jump around to your heart's content.\n\n.. code-block:: yaml\n\n steps:\n - name: pypyr.steps.echo\n in:\n echoMe: this is the 1st step of steps\n - name: pypyr.steps.jump\n in:\n jump: arbgroup\n - name: pypyr.steps.echo\n in:\n echoMe: You WON'T see me because we jumped.\n arbgroup:\n - name: pypyr.steps.echo\n in:\n echoMe: this is arb group\n - pypyr.steps.stopstepgroup\n - name: pypyr.steps.echo\n in:\n echoMe: if you see me something is WRONG.\n\n\nThis will result in:\n\n.. code-block:: text\n\n NOTIFY:pypyr.steps.echo:run_step: this is the 1st step of steps\n NOTIFY:pypyr.steps.echo:run_step: this is arb group\n\n\nJump only runs success or failure groups if you actually specify these.\n\nAll inputs support string `Substitutions`_.\n\nSee a worked example for `jump here\n`__.\n\npypyr.steps.pathcheck\n^^^^^^^^^^^^^^^^^^^^^\nCheck if a path exists on the filesystem. Supports globbing. A path can point\nto a file or a directory.\n\nThe ``pathCheck`` context key must exist.\n\n.. code-block:: yaml\n\n - name: pypyr.steps.pathcheck\n in:\n pathCheck: ./**/*.py # single path with glob\n\nIf you want to check for the existence of multiple paths, you can pass a list\ninstead. You can freely mix literal paths and globs.\n\n.. code-block:: yaml\n\n - name: pypyr.steps.pathcheck\n in:\n pathCheck:\n - ./file1 # literal relative path\n - ./dirname # also finds dirs\n - ./**/{arbkey}* # glob with a string formatting expression\n\nAfter *pathcheck* completes, the ``pathCheckOut`` context key is available.\nThis contains the results of the *pathcheck* operation.\n\n.. code-block:: yaml\n\n pathCheckOut:\n # the key is the ORIGINAL input, no string formatting applied.\n 'inpath-is-the-key': # one of these for each pathCheck input\n exists: true # bool. True if path exists.\n count: 0 # int. Number of files found for in path.\n found: ['path1', 'path2'] # list of strings. Paths of files found.\n\nExample of passing a single input and the expected output context:\n\n.. code-block:: yaml\n\n pathCheck: ./myfile # assuming ./myfile exists in $PWD\n pathCheckOut:\n './myfile':\n exists: true,\n count: 1,\n found:\n - './myfile'\n\nThe ``exists`` and ``count`` keys can be very useful for conditional\ndecorators to help decide whether to run subsequent steps. You can use these\ndirectly in string formatting expressions without any extra fuss.\n\n.. code-block:: yaml\n\n - name: pypyr.steps.pathcheck\n in:\n pathCheck: ./**/*.arb\n - name: pypyr.steps.echo\n run: '{pathCheckOut[./**/*.arb][exists]}'\n in:\n echoMe: you'll only see me if ./**/*.arb found something on filesystem.\n\nAll inputs support `Substitutions`_. This means you can specify another context\nitem to be an individual path, or part of a path, or the entire path list.\n\nSee a worked\nexample for `pathcheck here\n`_.\n\npypyr.steps.py\n^^^^^^^^^^^^^^\nExecutes the context value `pycode` as python code.\n\nWill exec ``context['pycode']`` as a dynamically interpreted python code block.\n\nYou can access and change the context dictionary in a py step. See a worked\nexample `here\n`_.\n\nFor example, this will invoke python print and print 2:\n\n.. code-block:: yaml\n\n steps:\n - name: pypyr.steps.py\n description: Example of an arb python command. Will print 2.\n in:\n pycode: print(1+1)\n\npypyr.steps.pype\n^^^^^^^^^^^^^^^^\nOverview\n\"\"\"\"\"\"\"\"\nRun another pipeline from this step. This allows pipelines to invoke other\npipelines. Why pype? Because the pypyr can pipe that song again.\n\n*pype* is handy if you want to split a larger, cumbersome pipeline into smaller\nunits. This helps testing, in that you can test smaller units as\nseparate pipelines without having to re-run the whole pipeline each time. This\ngets pretty useful for longer running sequences where the first steps are not\nidempotent but you do want to iterate over the last steps in the pipeline.\nProvisioning or deployment scripts frequently have this sort of pattern: where\nthe first steps provision expensive resources in the environment and later steps\njust tweak settings on the existing environment.\n\nThe parent pipeline is the current, executing pipeline. The invoked, or child,\npipeline is the pipeline you are calling from this step.\n\nSee here for worked example of `pype\n`_.\n\nContext properties\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\nExample input context:\n\n.. code-block:: yaml\n\n pype:\n name: 'pipeline name' # mandatory. string.\n args: # optional. Defaults None.\n inputkey: value\n anotherkey: anothervalue\n out: # optional. Defaults None.\n parentkey: childkey\n parentkey2: childkey2\n groups: [group1, group2] # optional. Defaults \"steps\".\n success: 'success group' # optional. Defaults \"on_success\".\n failure: 'failure group' # optional. Defaults \"on_failure\".\n pipeArg: 'argument here' # optional. string.\n raiseError: True # optional. bool. Defaults True.\n skipParse: True # optional. bool. Defaults True.\n useParentContext: True # optional. bool. Defaults True.\n loader: None # optional. string. Defaults to standard file loader.\n\n\nAll inputs supports string `Substitutions`_.\n\n+-----------------------+------------------------------------------------------+\n| **pype property** | **description** |\n+-----------------------+------------------------------------------------------+\n| name | Name of child pipeline to execute. This {name}.yaml |\n| | must exist in the *working directory* dir. |\n+-----------------------+------------------------------------------------------+\n| args | Run child pipeline with these args. These args |\n| | create a fresh context for the child pipeline that |\n| | contains only the key/values that you set here. |\n| | |\n| | If you set *args*, you implicitly set |\n| | *useParentContext* to False. If you explicitly set |\n| | *useParentContext* to True AND you specify *args*, |\n| | the args will be merged into the parent context |\n| | and {formatting expressions} applied before running |\n| | the child pipeline. |\n+-----------------------+------------------------------------------------------+\n| out | If the child pipeline ran with a fresh new Context, |\n| | because you set *args* or you set *useParentContext* |\n| | to False, *out* saves values from the child pipeline |\n| | context back to the parent context. |\n| | |\n| | *out* can take 3 forms: |\n| | |\n| | .. code-block:: yaml |\n| | |\n| | # save key1 from child to parent |\n| | out: 'key1' |\n| | # or save list of keys from child to parent |\n| | out: ['key1', 'key2'] |\n| | # or map child keys to different parent keys |\n| | out: |\n| | 'parent-destination-key1': 'child-key1' |\n| | 'parent-destination-key2': 'child-key2' |\n+-----------------------+------------------------------------------------------+\n| groups | Run only these step-groups in the child pipeline. |\n| | Equivalent to *groups* arg on the pypyr cli. |\n| | |\n| | If you don't set this, pypyr will just run the |\n| | *steps* step-group as per usual. |\n| | |\n| | If you only want to run a single group, you can set |\n| | it simply as a string, not a list, like this: |\n| | |\n| | ``groups: mygroupname`` |\n| | |\n| | If you set groups, success and failure do not default|\n| | to *on_success* and *on_failure* anymore. In other |\n| | words, pype will only run the groups you specifically|\n| | specified. If you still want success/failure handlers|\n| | explicitly set these with *success* & *failure*. |\n+-----------------------+------------------------------------------------------+\n| success | Run this step-group on successful completion of the |\n| | child pipeline's step *groups*. |\n| | |\n| | Equivalent to *success* arg on the pypyr cli. |\n| | |\n| | If you don't set this, pypyr will just run the |\n| | *on_success* step-group as per usual if it exists. |\n| | |\n| | If you specify *success*, but you don't set *groups*,|\n| | pypyr will default to running the standard *steps* |\n| | group as entry-point for the child pipeline. |\n+-----------------------+------------------------------------------------------+\n| failure | Run this step-group on an error occurring in the |\n| | child pipeline's step *groups*. |\n| | |\n| | Equivalent to *failure* arg on the pypyr cli. |\n| | |\n| | If you don't set this, pypyr will just run the |\n| | *on_failure* step-group as per usual if it exists. |\n| | |\n| | If you specify *failure*, but you don't set *groups*,|\n| | pypyr will default to running the standard *steps* |\n| | group as entry-point for the child pipeline. |\n+-----------------------+------------------------------------------------------+\n| pipeArg | String to pass to the child pipeline context_parser. |\n| | Equivalent to *context* arg on the pypyr cli. Only |\n| | used if skipParse==False |\n+-----------------------+------------------------------------------------------+\n| raiseError | If True, errors in child raised up to parent. |\n| | |\n| | If False, log and swallow any errors that happen |\n| | during the invoked pipeline's execution. Swallowing |\n| | means that the current/parent pipeline will carry on |\n| | with the next step even if an error occurs in the |\n| | invoked pipeline. |\n+-----------------------+------------------------------------------------------+\n| skipParse | If True, skip the context_parser on the invoked |\n| | pipeline. |\n| | |\n| | This is relevant if your child-pipeline uses a |\n| | context_parser to initialize context when you test |\n| | it in isolation by running it directly from the cli, |\n| | but when calling from a parent pipeline the parent |\n| | is responsible for creating the appropriate context. |\n+-----------------------+------------------------------------------------------+\n| useParentContext | If True, passes the parent's context to the child. |\n| | Any changes to the context by the child will be |\n| | available to the parent when the child completes. |\n| | |\n| | If False, the child creates its own, fresh context |\n| | that does not contain any of the parent's keys. The |\n| | child's context is destroyed upon completion of the |\n| | child pipeline and updates to the child context do |\n| | not reach the parent context. |\n+-----------------------+------------------------------------------------------+\n| loader | Load the child pipeline with this loader. The |\n| | default is the standard pypyr |\n| | pypyr.pypeloaders.fileloader, which looks for pypes |\n| | in the ./pipelines directory. |\n+-----------------------+------------------------------------------------------+\n\nRoll your own pype loaders\n\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\"\nA pype loader is responsible for loading a pipeline. By default pypyr gets\npypes from the local ./pipelines/pypename.yaml location.\n\nThe default pype loader is *pypyr.pypeloaders.fileloader*.\n\nIf you want to load pypes from somewhere else, like maybe a shared pype library,\nor implement caching, or maybe from something like s3, you can roll your own\npype loader.\n\n.. code-block:: python\n\n import logging\n from pypyr.errors import PipelineNotFoundError\n import pypyr.yaml\n\n # use pypyr logger to ensure loglevel is set correctly\n logger = logging.getLogger(__name__)\n\n def get_pipeline_definition(pipeline_name, working_dir):\n \"\"\"Open and parse the pipeline definition yaml.\n\n Parses pipeline yaml and returns dictionary representing the pipeline.\n\n pipeline_name is whatever is passed in from the shell like:\n pypyr pipelinename args\n\n Args:\n pipeline_name: string. Name of pipeline. This will be the file-name of\n the pipeline - i.e {pipeline_name}.yaml\n Passed in from the shell 1st positional argument.\n working_dir: path. passed in from the shell --dir switch.\n\n Returns:\n dict describing the pipeline, parsed from the pipeline yaml.\n\n Raises:\n PipelineNotFoundError: pipeline_name not found.\n\n \"\"\"\n logger.debug(\"starting\")\n\n # it's good form only to use .info and higher log levels when you must.\n # For .debug() being verbose is very much encouraged.\n logger.info(\"Your clever code goes here. . . \")\n\n yaml_file = your_clever_function_that_gets_a_filelike_object_from_somewhere()\n pipeline_definition = pypyr.yaml.get_pipeline_yaml(yaml_file)\n\n logger.debug(\n f\"found {len(pipeline_definition)} stages in pipeline.\")\n\n logger.debug(\"pipeline definition loaded\")\n\n logger.debug(\"done\")\n return pipeline_definition\n\nRecursion\n\"\"\"\"\"\"\"\"\"\nYes, you can pype recursively - i.e a child pipeline can call its antecedents.\nIt's up to you to avoid infinite recursion, though. Since we're all responsible\nadults here, pypyr does not protect you from infinite recursion other than the\ndefault python recursion limit. So don't come crying if you blew your stack. Or\na seal.\n\nHere is a worked example of `pype recursion\n`_.\n\npypyr.steps.pypyrversion\n^^^^^^^^^^^^^^^^^^^^^^^^\nOutputs the same as:\n\n.. code-block:: bash\n\n pypyr --version\n\nThis is an actual pipeline, though, so unlike --version, it'll use the standard\npypyr logging format.\n\nExample pipeline yaml:\n\n.. code-block:: yaml\n\n steps:\n - pypyr.steps.pypyrversion\n\npypyr.steps.now\n^^^^^^^^^^^^^^^\nWrites the current local date & time to context *now*. Also known as wall time.\n\nIf you want UTC time, check out `pypyr.steps.nowutc`_ instead.\n\nIf you run this step as a simple step (with no input *nowIn* formatting), the\ndefault datetime format is ISO8601. For example:\n*YYYY-MM-DDTHH:MM:SS.ffffff+00:00*\n\nYou can use explicit format strings to control the datetime representation. For\na full list of available formatting codes, check here:\nhttps://docs.python.org/3.7/library/datetime.html#strftime-and-strptime-behavior\n\n.. code-block:: yaml\n\n - pypyr.steps.now # this sets {now} to YYYY-MM-DDTHH:MM:SS.ffffff+00:00\n - name: pypyr.steps.echo\n in:\n echoMe: 'timestamp in ISO8601 format: {now}'\n - name: pypyr.steps.now\n description: use a custom date format string instead of the default ISO8601\n in:\n nowIn: '%A %Y %m/%d %H:%M in timezone %Z offset %z, localized to %x'\n - name: pypyr.steps.echo\n in:\n echoMe: 'the custom formatting for now was set in the previous step. {now}'\n - pypyr.steps.now # subsequent simple step calls will re-use previously set\n # nowIn for formatting, but refresh the timestamp.\n\n\nSupports string `Substitutions`_.\n\nSee a worked example for `now here\n`__.\n\npypyr.steps.nowutc\n^^^^^^^^^^^^^^^^^^\nWrites the current UTC date & time to context *nowUtc*.\n\nIf you want local or wall time, check out `pypyr.steps.now`_ instead.\n\nIf you run this step as a simple step (with no input *nowUtcIn* formatting), the\ndefault datetime format is ISO8601. For example:\n*YYYY-MM-DDTHH:MM:SS.ffffff+00:00*\n\nYou can use explicit format strings to control the datetime representation. For\na full list of available formatting codes, check here:\nhttps://docs.python.org/3.7/library/datetime.html#strftime-and-strptime-behavior\n\n.. code-block:: yaml\n\n - pypyr.steps.nowutc # this sets {nowUtc} to YYYY-MM-DDTHH:MM:SS.ffffff+00:00\n - name: pypyr.steps.echo\n in:\n echoMe: 'utc timestamp in ISO8601 format: {nowUtc}'\n - name: pypyr.steps.nowutc\n description: use a custom date format string instead of the default ISO8601\n in:\n nowUtcIn: '%A %Y %m/%d %H:%M in timezone %Z offset %z, localized to %x'\n - name: pypyr.steps.echo\n in:\n echoMe: 'the custom formatting was set in the previous step: {nowUtc}'\n - pypyr.steps.nowutc # subsequent simple step calls will re-use previously set\n # nowUtcIn for formatting, but refresh the timestamp.\n\n\nSupports string `Substitutions`_.\n\nSee a worked example for `nowutc here\n`__.\n\npypyr.steps.safeshell\n^^^^^^^^^^^^^^^^^^^^^\nAlias for `pypyr.steps.cmd`_.\n\nExample pipeline yaml:\n\n.. code-block:: yaml\n\n steps:\n - name: pypyr.steps.safeshell\n in:\n cmd: ls -a\n\npypyr.steps.shell\n^^^^^^^^^^^^^^^^^\nRuns the context value `cmd` in the default shell. On a sensible O/S, this is\n`/bin/sh`\n\nDo all the things you can't do with `pypyr.steps.cmd`_.\n\nInput context can take one of two forms:\n\n.. code-block:: yaml\n\n - name: pypyr.steps.shell\n description: passing cmd as a string does not save the output to context.\n it prints stdout in real-time.\n in:\n cmd: 'echo ${PWD}'\n - name: pypyr.steps.shell\n description: passing cmd as a dict allows you to specify if you want to\n save the output to context.\n it prints command output only AFTER it has finished running.\n in:\n cmd:\n run: 'echo ${PWD}'\n save: True\n cwd: './current/working/dir/here'\n\nIf ``cwd`` is specified, will change the current working directory to *cwd* to\nexecute this command. The directory change is only for the duration of this\nstep, not any subsequent steps. If *cwd* is specified, the executable or program\nspecified in *run* is relative to the *cwd* if the *run* cmd uses relative paths.\n\nIf ``cwd`` is not specified, defaults to the current working directory, which\nis from wherever you are running ``pypyr``.\n\nBe aware that if *save* is True, all of the command output ends up in memory.\nDon't specify it unless your pipeline uses the stdout/stderr response in\nsubsequent steps. Keep in mind that if the invoked command return code returns\na non-zero return code pypyr will automatically raise a *CalledProcessError*\nand stop the pipeline.\n\nIf save is True, pypyr will save the output to context as follows:\n\n.. code-block:: yaml\n\n cmdOut:\n returncode: 0\n stdout: 'stdout str here. None if empty.'\n stderr: 'stderr str here. None if empty.'\n\n*cmdOut.returncode* is the exit status of the called process. Typically 0 means\nOK. A negative value -N indicates that the child was terminated by signal N\n(POSIX only).\n\nYou can use cmdOut in subsequent steps like this:\n\n.. code-block:: yaml\n\n - name: pypyr.steps.echo\n run: !py \"cmdOut['returncode'] == 0\"\n in:\n echoMe: \"you'll only see me if cmd ran successfully with return code 0.\n the command output was: {cmdOut[stdout]}\"\n\nFriendly reminder of the difference between separating your commands with ; or\n&&:\n\n- ; will continue to the next statement even if the previous command errored.\n It won't exit with an error code if it wasn't the last statement.\n- && stops and exits reporting error on first error.\n\nYou can change directory multiple times during this shell step using ``cd``,\nbut dir changes are only in scope for subsequent commands in this step, not for\nsubsequent steps. Instead prefer using the ``cwd`` input as described above for\nan easy life, which sets the working directory for the entire step without you\nhaving to code it in with chained shell commands.\n\n.. code-block:: yaml\n\n - name: pypyr.steps.shell\n description: hop one up from current working dir. sic means won't attempt\n to substitute {PWD} from context.\n in:\n cmd: !sic echo ${PWD}; cd ../; echo ${PWD}\n - name: pypyr.steps.shell\n description: back to your current working dir\n in:\n cmd: !sic echo ${PWD}\n\nSupports string `Substitutions`_.\n\nExample pipeline yaml using a pipe:\n\n.. code-block:: bash\n\n steps:\n - name: pypyr.steps.shell\n in:\n cmd: ls | grep pipe; echo if you had something pipey it should show up;\n - name: pypyr.steps.shell\n description: if you want to pass curlies to the shell, use sic strings\n in:\n cmd: !sic echo ${PWD};\n\nSee a worked example `for shell power here\n`__.\n\npypyr.steps.stop\n^^^^^^^^^^^^^^^^\nStop all pypyr processing immediately. Doesn't run any success or failure\nhandlers, it just stops everything in its tracks, even when you're nested\nin child pipelines or a step-group call-chain.\n\nYou can always use ``pypyr.steps.stop`` as a simple step.\n\n.. code-block:: yaml\n\n - name: pypyr.steps.echo\n in:\n echoMe: you'll see me...\n - pypyr.steps.stop\n - name: pypyr.steps.echo\n in:\n echoMe: you WON'T see me...\n\n\nSee a worked example `for stop here\n`__.\n\n\npypyr.steps.stoppipeline\n^^^^^^^^^^^^^^^^^^^^^^^^\nStop current pipeline. Doesn't run any success or failure handlers, it just\nstops the current pipeline.\n\nThis is handy if you are using ``pypyr.steps.pype`` to call child pipelines\nfrom a parent pipeline, allowing you to stop just a child pipeline but letting\nthe parent pipeline continue.\n\nYou can always use ``pypyr.steps.stoppipeline`` as a simple step.\n\n.. code-block:: yaml\n\n - name: pypyr.steps.echo\n in:\n echoMe: you'll see me...\n - pypyr.steps.stoppipeline\n - name: pypyr.steps.echo\n in:\n echoMe: you WON'T see me...\n\n\nSee a worked example `for stop pipeline here\n`__.\n\npypyr.steps.stopstepgroup\n^^^^^^^^^^^^^^^^^^^^^^^^^\nStop current step-group. Doesn't run any success or failure handlers, it just\nstops the current step-group.\n\nThis is handy if you are using ``pypyr.steps.call`` or ``pypyr.steps.jump``\nto run different step-groups, allowing you to stop just a child step-group but\nletting the parent step-group continue.\n\nYou can always use ``pypyr.steps.stopstepgroup`` as a simple step.\n\n.. code-block:: yaml\n\n steps:\n - name: pypyr.steps.call\n in:\n call:\n groups: arbgroup\n - name: pypyr.steps.echo\n in:\n echoMe: You'll see me because only arbgroup was stopped.\n\n arbgroup:\n - name: pypyr.steps.echo\n in:\n echoMe: this is arb group\n - pypyr.steps.stopstepgroup\n - name: pypyr.steps.echo\n in:\n echoMe: if you see me something is WRONG.\n\n\nSee a worked example `for stop step-group here\n`__.\n\npypyr.steps.tar\n^^^^^^^^^^^^^^^\nArchive and/or extract tars with or without compression.\n\n.. code-block:: yaml\n\n tar:\n extract:\n - in: /path/my.tar\n out: /out/path\n archive:\n - in: /dir/to/archive\n out: /out/destination.tar\n format: ''\n\nEither ``extract`` or ``archive`` should exist, or both. But not neither.\n\nOptionally, you can also specify the tar compression format with\n``format``. If not specified, defaults to *lzma/xz*\nAvailable options for ``format``:\n\n- ``''`` - no compression\n- ``gz`` (gzip)\n- ``bz2`` (bzip2)\n- ``xz`` (lzma)\n\nThis step will run whatever combination of Extract and Archive you specify.\nRegardless of combination, execution order is Extract, then Archive.\n\nNever extract archives from untrusted sources without prior inspection. It is\npossible that files are created outside of path, e.g. members that have\nabsolute filenames starting with \"/\" or filenames with two dots \"..\".\n\nSee a worked example `for tar here\n`__.\n\ntar extract\n\"\"\"\"\"\"\"\"\"\"\"\n``tar['extract']`` must exist. It's a list of dictionaries.\n\nkeys are the path to the tar to extract.\n\nvalues are the destination paths.\n\nYou can use {key} substitutions to format the string from context. See\n`Substitutions`_.\n\n.. code-block:: yaml\n\n key1: here\n key2: tar.xz\n tar:\n extract:\n - in: path/to/my.tar.xz\n out: /path/extract/{key1}\n - in: another/{key2}\n out: .\n\nThis will:\n\n- Extract *path/to/my.tar.xz* to */path/extract/here*\n- Extract *another/tar.xz* to the current execution directory\n\n - This is the directory you're running pypyr from, not the pypyr pipeline\n working directory you set with the ``--dir`` flag.\n\ntar archive\n\"\"\"\"\"\"\"\"\"\"\"\n``tar['archive']`` must exist. It's a list of dictionaries.\n\nkeys are the paths to archive.\n\nvalues are the destination output paths.\n\nYou can use {key} substitutions to format the string from context. See\n`Substitutions`_.\n\n.. code-block:: yaml\n\n key1: destination.tar.xz\n key2: value2\n tar:\n archive:\n - in: path/{key2}/dir\n out: path/to/{key1}\n - in: another/my.file\n out: ./my.tar.xz\n\nThis will:\n\n- Archive directory *path/value2/dir* to *path/to/destination.tar.xz*,\n- Archive file *another/my.file* to *./my.tar.xz*\n\n\nRoll your own step\n------------------\n.. code-block:: python\n\n import logging\n\n\n # getLogger will grab the parent logger context, so your loglevel and\n # formatting will inherit correctly automatically from the pypyr core.\n logger = logging.getLogger(__name__)\n\n\n def run_step(context):\n \"\"\"Run code in here. This shows you how to code a custom pipeline step.\n\n :param context: dictionary-like type\n \"\"\"\n logger.debug(\"started\")\n # you probably want to do some asserts here to check that the input context\n # dictionary contains the keys and values you need for your code to work.\n assert 'mykeyvalue' in context, (\"context['mykeyvalue'] must exist for my clever step.\")\n\n # it's good form only to use .info and higher log levels when you must.\n # For .debug() being verbose is very much encouraged.\n logger.info(\"Your clever code goes here. . . \")\n\n # Add or edit context items. These are available to any pipeline steps\n # following this one.\n context['existingkey'] = 'new value overwrites old value'\n context['mynewcleverkey'] = 'new value'\n\n logger.debug(\"done\")\n\non_success\n==========\non_success is a list of steps to execute in sequence. Runs when `steps:`\ncompletes successfully.\n\nYou can use built-in steps or code your own steps exactly like you would for\nsteps - it uses the same function signature.\n\non_failure\n==========\non_failure is a list of steps to execute in sequence. Runs when any of the\nabove hits an unhandled exception.\n\nIf on_failure encounters another exception while processing an exception, then\nboth that exception and the original cause exception will be logged.\n\nYou can use built-in steps or code your own steps exactly like you would for\nsteps - it uses the same function signature.\n\n******\nErrors\n******\n*pypyr* runs pipelines. . . and a pipeline is a sequence of steps. Philosophically,\n*pypyr* assumes that any error is a hard stop, unless you explicitly tell\n*pypyr* differently.\n\n*pypyr* saves all run-time errors to a list in context called *runErrors*.\n\n.. code-block:: yaml\n\n runErrors:\n - name: Error Name Here\n description: Error Description Here\n customError: # whatever you put into onError on step definition\n line: 1 # line in pipeline yaml for failing step\n col: 1 # column in pipeline yaml for failing step\n step: my.bad.step.name # failing step name\n exception: ValueError('arb') # the actual python error object\n swallowed: False # True if err was swallowed\n\nThe last error will be the last item in the list. The first error will be the\nfirst item in the list.\n\nThis is handy if you use the *swallow* step decorator to swallow an error or\nbunch of errors, but you still want to do things in subsequent steps with the\nerror information.\n\n*************\nSubstitutions\n*************\nstring interpolation\n====================\nYou can use substitution tokens, aka string interpolation, where specified for\ncontext items. This substitutes anything between {curly braces} with the\ncontext value for that key. This also works where you have dictionaries/lists\ninside dictionaries/lists. For example, if your context looked like this:\n\n.. code-block:: yaml\n\n key1: down\n key2: valleys\n key3: value3\n key4: \"Piping {key1} the {key2} wild\"\n\nThe value for ``key4`` will be \"Piping down the valleys wild\".\n\nEscape literal curly braces with doubles: {{ for {, }} for }\n\nIn json & yaml, curlies need to be inside quotes to make sure they parse as\nstrings. Especially watch in .yaml, where { as the first character of a key or\nvalue will throw a formatting error if it's not in quotes like this:\n*\"{key}\"*\n\nYou can also reference keys nested deeper in the context hierarchy, in cases\nwhere you have a dictionary that contains lists/dictionaries that might contain\nother lists/dictionaries and so forth.\n\n.. code-block:: yaml\n\n root:\n - list index 0\n - key1: this is a value from a dict containing a list, which contains a dict at index 1\n key2: key 2 value\n - list index 1\n\nGiven the context above, you can use formatting expressions to access nested\nvalues like this:\n\n.. code-block:: text\n\n '{root[0]}' == list index 0\n '{root[1][key1]}' == this is a value from a dict containing a list, which contains a dict at index 1\n '{root[1][key2]}' == key 2 value\n '{root[2]}' == list index 1\n\n\npy strings\n==========\npy strings allow you to execute python expressions dynamically. This allows you\nto use a python expression wherever you can use a string formatting expression.\n\nA py string looks like this:\n\n.. code-block:: text\n\n !py <>\n\n\nFor example, if ``context['key']`` is 'abc', the following will return True:\n``!py len(key) == 3\"``\n\nThe Py string expression has the usual python builtins available to it, in\naddition to the Context dictionary. In other words, you can use functions like\n``abs``, ``len`` - full list here\nhttps://docs.python.org/3/library/functions.html.\n\nNotice that you can use the context keys directly as variables. Unlike string\nformatting expressions, you don't surround the key name with {curlies}.\n\nIn pipeline yaml, if the first character of the py string is a yaml structural\ncharacter, you should put the Py string in quotes or as part of a literal block.\n\n.. code-block:: yaml\n\n - name: pypyr.steps.echo\n description: don't run this step if int > 4.\n No need to wrap the expression in extra quotes!\n run: !py thisIsAnInt < 5\n in:\n echoMe: you'll see me if context thisIsAnInt is less than 5.\n - name: pypyr.steps.echo\n description: only run this step if breakfast includes spam\n since the first char is a single quote, wrap the Py string in\n double quotes to prevent malformed yaml.\n run: !py \"'spam' in ['eggs', 'spam', 'bacon']\"\n in:\n echoMe: you should see me because spam is in breakfast!\n\nSee a worked example `for py strings here\n`__.\n\nsic strings\n===========\nIf a string is NOT to have {substitutions} run on it, it's *sic erat scriptum*,\nor *sic* for short. This is handy especially when you are dealing with json\nas a string, rather than an actual json object, so you don't have to double\ncurly all the structural braces.\n\nA *sic* string looks like this:\n\n.. code-block:: text\n\n !sic <>\n\nFor example:\n\n.. code-block:: text\n\n !sic piping {key} the valleys wild\n\nWill return \"piping {key} the valleys wild\" without attempting to substitute\n{key} from context. You can happily use \", ' or {} inside a ``!sic my string``\nstring without escaping these any further. This makes sic strings ideal for\nstrings containing json.\n\nYou can surround the Sic string with single or double quotes like this\n``!sic 'my string here'`` or ``!sic \"my string here\"``. This is handy if your\nstring starts with a yaml structural character like square [ or curly { braces.\nCheck example below for escape sequences if you do so.\n\n.. code-block:: yaml\n\n - name: pypyr.steps.echo\n description: >\n use a sic string not to format any {values}. Do watch the\n use of the yaml literal with block chomping indicator |- to\n prevent the last character in the string from being a LF.\n in:\n echoMe: !sic |-\n\n {\n \"key1\": \"key1 value with a {curly}\"\n }\n - name: pypyr.steps.echo\n description: use a sic string not to format any {values} on one line. No need to escape further quotes.\n in:\n echoMe: !sic string with a {curly} with \", ' and & and double quote at end:\"\n - name: pypyr.steps.echo\n description: use a sic string with single quotes.\n in:\n echoMe: !sic '{string} with {curlies} inside single quotes, : colon, quote \", backslash \\.'\n - name: pypyr.steps.echo\n description: use a sic string with double quotes. Double up the backslashes!\n in:\n echoMe: !sic \"[string] with {curlies} inside double quotes, : colon, quote \\\", backslash \\\\.\"\n\n\nYou can pick single or double quotes, so just go with whichever is less annoying\nfor your particular string.\n\nSee a worked example `for substitutions here\n`__.\n\n********\nPlug-Ins\n********\nThe pypyr core is deliberately kept light so the dependencies are down to the\nminimum. I loathe installs where there\\'re a raft of extra deps that I don\\'t\nuse clogging up the system.\n\nWhere other libraries are requisite, you can selectively choose to add this\nfunctionality by installing a pypyr plug-in.\n\n+----------------------------+-------------------------------------------------+\n| **boss pypyr plug-ins** | **description** |\n+----------------------------+-------------------------------------------------+\n| |pypyr-aws| | Interact with the AWS sdk api. Supports all AWS |\n| | Client functions, such as S3, EC2, ECS & co. |\n| | via the AWS low-level Client API. |\n+----------------------------+-------------------------------------------------+\n| |pypyr-slack| | Send messages to Slack |\n+----------------------------+-------------------------------------------------+\n\n*****\nHelp!\n*****\nDon't Panic! For help, community or talk, join the chat on |discord|!\n\n**********\nContribute\n**********\nDevelopers\n==========\nFor information on how to help with pypyr, run tests and coverage, please do\ncheck out the `contribution guide `_.\n\nBugs\n====\nWell, you know. No one's perfect. Feel free to `create an issue\n`_.\n\n**********\nThank yous\n**********\npypyr is fortunate to stand on the shoulders of a giant in the shape of the\nexcellent `ruamel.yaml `_ library by\nAnthon van der Neut for all yaml parsing and validation.\n\n.. |build-status| image:: https://api.shippable.com/projects/58efdfe130eb380700e559a6/badge?branch=master\n :alt: build status\n :target: https://app.shippable.com/github/pypyr/pypyr-cli\n\n.. |coverage| image:: https://api.shippable.com/projects/58efdfe130eb380700e559a6/coverageBadge?branch=master\n :alt: coverage status\n :target: https://app.shippable.com/github/pypyr/pypyr-cli\n\n.. |pypi| image:: https://badge.fury.io/py/pypyr.svg\n :alt: pypi version\n :target: https://pypi.python.org/pypi/pypyr/\n :align: bottom\n\n.. |pypyr-aws| replace:: `pypyr-aws `__\n\n.. |pypyr-slack| replace:: `pypyr-slack `__\n\n.. |discord| replace:: `discord `__\n\n\n", "description_content_type": "", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/pypyr/pypyr-cli", "keywords": "devops pipeline runner", "license": "Apache License 2.0", "maintainer": "", "maintainer_email": "", "name": "pypyr", "package_url": "https://pypi.org/project/pypyr/", "platform": "", "project_url": "https://pypi.org/project/pypyr/", "project_urls": { "Homepage": "https://github.com/pypyr/pypyr-cli" }, "release_url": "https://pypi.org/project/pypyr/3.0.1/", "requires_dist": [ "python-dateutil", "ruamel.yaml", "bumpversion; extra == 'deploy'", "twine; extra == 'deploy'", "check-manifest; extra == 'dev'", "flake8; extra == 'dev'", "pytest; extra == 'test'", "tox; extra == 'test'" ], "requires_python": ">=3.6", "summary": "pipeline runner command line to run pipelines defined in yaml", "version": "3.0.1" }, "last_serial": 5827584, "releases": { "0.0.10": [ { "comment_text": "", "digests": { "md5": "5f540b4811013fa58b521503c80e4b7c", "sha256": "090271e7d223414ea1a3f67f651432edaf8699d994a6b7911a1c8b164b6f96eb" }, "downloads": -1, "filename": "pypyr-0.0.10-py3-none-any.whl", "has_sig": true, "md5_digest": "5f540b4811013fa58b521503c80e4b7c", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 18887, "upload_time": "2017-03-27T16:46:14", "url": "https://files.pythonhosted.org/packages/8c/b4/b42fdd266c5aefdbd87a076637950a29f6792eedd5d62f35fb1ada5708b8/pypyr-0.0.10-py3-none-any.whl" } ], "0.0.11": [ { "comment_text": "", "digests": { "md5": "4c9f953d92e1c62b5217d4d942fd2827", "sha256": "909f22bc5b3eea71eba81084dd6c67c4edf86eb68b3c7fed43c301e5013046b3" }, "downloads": -1, "filename": "pypyr-0.0.11-py3-none-any.whl", "has_sig": true, "md5_digest": "4c9f953d92e1c62b5217d4d942fd2827", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 32201, "upload_time": "2017-04-08T12:48:27", "url": "https://files.pythonhosted.org/packages/91/fa/e93f4ce43fb6f770e912e00bf6585b5252977fa12c5bb61a244bd1d8d599/pypyr-0.0.11-py3-none-any.whl" } ], "0.0.9": [], "0.1.0": [ { "comment_text": "", "digests": { "md5": "b034b42e70741eaa2f8f21a9a6a592bb", "sha256": "f260aee66ffc19d6ab3fe96b5f9f4c613c595b7b15e36b31b4b917957d7f9ae8" }, "downloads": -1, "filename": "pypyr-0.1.0-py3-none-any.whl", "has_sig": true, "md5_digest": "b034b42e70741eaa2f8f21a9a6a592bb", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 32181, "upload_time": "2017-04-08T13:06:31", "url": "https://files.pythonhosted.org/packages/d6/4e/c68dc47267fdb7590b7b8bac0ea0a6e5c70b976b36d0658efc402c62fff7/pypyr-0.1.0-py3-none-any.whl" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "29e8885b68faa95b4ceaf90227ea3ffc", "sha256": "97250bce8c8539cf0f8b47415eef1fd517c992a9988961b9008341a906633407" }, "downloads": -1, "filename": "pypyr-0.1.1-py3-none-any.whl", "has_sig": true, "md5_digest": "29e8885b68faa95b4ceaf90227ea3ffc", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 32217, "upload_time": "2017-04-08T13:26:18", "url": "https://files.pythonhosted.org/packages/48/ac/de7f751170fd4aa9f0dd01298acecd3e1ef43abcabe7052a073a02362cfb/pypyr-0.1.1-py3-none-any.whl" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "4c29403714eb94b10f2d9081472f4f55", "sha256": "24346847a1f588df0239b9bd975dc07e3913e399b26db1ecaa3061b869f09b48" }, "downloads": -1, "filename": "pypyr-0.1.2-py3-none-any.whl", "has_sig": true, "md5_digest": "4c29403714eb94b10f2d9081472f4f55", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 34225, "upload_time": "2017-04-11T13:24:17", "url": "https://files.pythonhosted.org/packages/ac/2d/3d468dc2b4c1de9ab72341d497994dc7786d7d8694b125dc10c93ab991e2/pypyr-0.1.2-py3-none-any.whl" } ], "0.1.3": [ { "comment_text": "", "digests": { "md5": "eed1afed4de7a3407db6655e41f36ffb", "sha256": "7b19aca32e5052b6af8f46bfaae20b87606a38ea3c3a23d94430a7ef69bb6d00" }, "downloads": -1, "filename": "pypyr-0.1.3-py3-none-any.whl", "has_sig": true, "md5_digest": "eed1afed4de7a3407db6655e41f36ffb", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 34921, "upload_time": "2017-04-11T21:22:25", "url": "https://files.pythonhosted.org/packages/d5/da/ff2a7d95794f71c0c196002c24d2bb037730a942d4a64400ec921223203c/pypyr-0.1.3-py3-none-any.whl" } ], "0.1.4": [ { "comment_text": "", "digests": { "md5": "fbc9beec54dd6291efbf6809a97289fd", "sha256": "a8f8a7a162cdcfbc01b7b68ea2b1d21a975b1a19a6599e8c1bf3d8f3d356678c" }, "downloads": -1, "filename": "pypyr-0.1.4-py3-none-any.whl", "has_sig": true, "md5_digest": "fbc9beec54dd6291efbf6809a97289fd", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 35007, "upload_time": "2017-04-12T09:43:25", "url": "https://files.pythonhosted.org/packages/c7/78/8377c2b63ffce11ac4eb84bafbd0329f9abdcdf023b7b39d1badd6adc59d/pypyr-0.1.4-py3-none-any.whl" } ], "0.1.5": [ { "comment_text": "", "digests": { "md5": "99d8045a17d86118a8eaaf78586967a5", "sha256": "e16d11e900c8ebd2a878ed32a66f0af2bc1b112799a8bc8b772f105756011773" }, "downloads": -1, "filename": "pypyr-0.1.5-py3-none-any.whl", "has_sig": true, "md5_digest": "99d8045a17d86118a8eaaf78586967a5", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 36142, "upload_time": "2017-04-12T17:32:30", "url": "https://files.pythonhosted.org/packages/31/d1/d1e8643151de0a84dc16facc333ffd4017ee376aca1743fbf00ba68914fe/pypyr-0.1.5-py3-none-any.whl" } ], "0.10.0": [ { "comment_text": "", "digests": { "md5": "273a8f98133696a30e15641b3d434dc5", "sha256": "2b364f0a1f04ea9d43ee12766ed184632aa9ab368a57d1e84eea6cbbcd9dbf4c" }, "downloads": -1, "filename": "pypyr-0.10.0-py3-none-any.whl", "has_sig": false, "md5_digest": "273a8f98133696a30e15641b3d434dc5", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 58084, "upload_time": "2018-07-18T20:25:24", "url": "https://files.pythonhosted.org/packages/60/3e/e3239666125b7f15fd48325630056a6db04089c9e4a0973db4b5b19c7c00/pypyr-0.10.0-py3-none-any.whl" } ], "0.11.0": [ { "comment_text": "", "digests": { "md5": "638881bbcda2020173d0e355ddcb7851", "sha256": "3bf1d8e961d6ebe412b40fe2dba9c8de49d7a85c874197d3ec8552dcd6e2be9c" }, "downloads": -1, "filename": "pypyr-0.11.0-py3-none-any.whl", "has_sig": false, "md5_digest": "638881bbcda2020173d0e355ddcb7851", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 61079, "upload_time": "2018-07-23T23:41:49", "url": "https://files.pythonhosted.org/packages/d3/ed/5f727a8d4438465b4d22e447ecd7852f87ee37642c494df6a35764aa9b6d/pypyr-0.11.0-py3-none-any.whl" } ], "0.2.0": [ { "comment_text": "", "digests": { "md5": "2d6eef8f7937643290ae4589abc364b1", "sha256": "2dc83f6c1a57c52bd9ce588e460ebe162aa3df9d398b5ee91e282c48364afd48" }, "downloads": -1, "filename": "pypyr-0.2.0-py3-none-any.whl", "has_sig": true, "md5_digest": "2d6eef8f7937643290ae4589abc364b1", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 36286, "upload_time": "2017-04-13T10:10:48", "url": "https://files.pythonhosted.org/packages/2f/db/569a19eb3bb103aaa93f5efb093d87011d29499750078d39e1e76b451be0/pypyr-0.2.0-py3-none-any.whl" } ], "0.2.2": [ { "comment_text": "", "digests": { "md5": "2e31db5bf1157e9ecaabb3d47c3485b4", "sha256": "bd282edaf0ebf313056f338120898887d7e15d6bdf9684a2b4c1d6ba78cc5a1a" }, "downloads": -1, "filename": "pypyr-0.2.2-py3-none-any.whl", "has_sig": false, "md5_digest": "2e31db5bf1157e9ecaabb3d47c3485b4", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 29114, "upload_time": "2017-04-18T15:08:23", "url": "https://files.pythonhosted.org/packages/4b/55/a380d8d55520e171b9af177f5a5574a841eaf902f41c563e3717dc13a976/pypyr-0.2.2-py3-none-any.whl" } ], "0.2.3": [ { "comment_text": "", "digests": { "md5": "a53561861968001be80a6053947b5628", "sha256": "53f06ae7b3db037bdff5dbb8a735d5c914c706592e82d51c345857b651d12811" }, "downloads": -1, "filename": "pypyr-0.2.3-py3-none-any.whl", "has_sig": false, "md5_digest": "a53561861968001be80a6053947b5628", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 29113, "upload_time": "2017-04-18T16:52:11", "url": "https://files.pythonhosted.org/packages/9f/6f/d99248611b58ef7f623bd0226e2a74307603455f89622e2beda0f7c24916/pypyr-0.2.3-py3-none-any.whl" } ], "0.3.0": [ { "comment_text": "", "digests": { "md5": "49bc6645aac164b9b38b4b7a23877d23", "sha256": "45d921cf21da7b970b7e094a3062aa35c057cfc06cb5c7e48abf53aae35d2420" }, "downloads": -1, "filename": "pypyr-0.3.0-py3-none-any.whl", "has_sig": false, "md5_digest": "49bc6645aac164b9b38b4b7a23877d23", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 35575, "upload_time": "2017-04-24T14:45:45", "url": "https://files.pythonhosted.org/packages/19/04/fc379488c9678ad33f420635d3c8a0183fcf8be48de4d88d8afdae754a99/pypyr-0.3.0-py3-none-any.whl" } ], "0.3.1": [ { "comment_text": "", "digests": { "md5": "edd5ba2f5f4ee79fbb8cc6a2314f23e4", "sha256": "6d9eebc624ec8c173248c15ebff727695095c02f24d9bd37eb5134621094a3d5" }, "downloads": -1, "filename": "pypyr-0.3.1-py3-none-any.whl", "has_sig": false, "md5_digest": "edd5ba2f5f4ee79fbb8cc6a2314f23e4", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 35694, "upload_time": "2017-04-25T16:46:44", "url": "https://files.pythonhosted.org/packages/0b/fc/7c814902c03a71e50a7ab7624c6fa510aa209f8e5f3dc41593468a95eb8e/pypyr-0.3.1-py3-none-any.whl" } ], "0.3.2": [ { "comment_text": "", "digests": { "md5": "d4d2d81a08207141bb0eb48e87faa165", "sha256": "81c4ac1ce222483af2b6e3f04b22e3be1853aaa52207bcb9394cc270b7045b39" }, "downloads": -1, "filename": "pypyr-0.3.2-py3-none-any.whl", "has_sig": false, "md5_digest": "d4d2d81a08207141bb0eb48e87faa165", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 35987, "upload_time": "2017-04-26T09:38:32", "url": "https://files.pythonhosted.org/packages/75/08/8e528f1488eb1a32ace08caa05310d90e286bacca54fa19484b5486ad21b/pypyr-0.3.2-py3-none-any.whl" } ], "0.4.0": [ { "comment_text": "", "digests": { "md5": "8a0c219fb5329c661b971f6b1a4c8d28", "sha256": "6600466de4d886c226b23f2589f8d016c0cb4f3bd48e6ae6567c70667dc8f77d" }, "downloads": -1, "filename": "pypyr-0.4.0-py3-none-any.whl", "has_sig": false, "md5_digest": "8a0c219fb5329c661b971f6b1a4c8d28", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 36356, "upload_time": "2017-04-27T18:37:04", "url": "https://files.pythonhosted.org/packages/36/b6/d56299d6db5dbfedc4cdf150074796e266313193be8c80e479dd6fecb1bc/pypyr-0.4.0-py3-none-any.whl" } ], "0.4.1": [ { "comment_text": "", "digests": { "md5": "5c3d682990790468c8716ed14eb7a2a1", "sha256": "d65e39a90cbb047f41966f73e0651ee142dd92932bd9f02caa704b1af5aa1550" }, "downloads": -1, "filename": "pypyr-0.4.1-py3-none-any.whl", "has_sig": false, "md5_digest": "5c3d682990790468c8716ed14eb7a2a1", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 36821, "upload_time": "2017-04-28T08:41:31", "url": "https://files.pythonhosted.org/packages/85/47/561fb8fe73bf15f2eb304f08128e0b9fe7db046efd12c02faaa11f006c32/pypyr-0.4.1-py3-none-any.whl" } ], "0.5.0": [ { "comment_text": "", "digests": { "md5": "41c12cd76542c713a212cdec02c9d7ef", "sha256": "bc3437546077caf83effe7cc287439face8004bcef0752d21858b8f50ea60a61" }, "downloads": -1, "filename": "pypyr-0.5.0-py3-none-any.whl", "has_sig": false, "md5_digest": "41c12cd76542c713a212cdec02c9d7ef", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 37110, "upload_time": "2017-04-28T15:15:42", "url": "https://files.pythonhosted.org/packages/eb/67/d0feba65a07e6197dd5a7ea758c8211ecc12265e9a9c932b60723bf26b34/pypyr-0.5.0-py3-none-any.whl" } ], "0.5.1": [ { "comment_text": "", "digests": { "md5": "a3815d45e72dc15ff38d17d648f66865", "sha256": "caaac83a41c320856d618c265ed435e18e199e3c1d7d2d20e042454fa35b2e77" }, "downloads": -1, "filename": "pypyr-0.5.1-py3-none-any.whl", "has_sig": false, "md5_digest": "a3815d45e72dc15ff38d17d648f66865", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 42492, "upload_time": "2017-05-02T10:19:03", "url": "https://files.pythonhosted.org/packages/35/ac/f447921478724a1d5bf89091dc1e694df1e1d0ef3df5d22cc6dfc7f7662c/pypyr-0.5.1-py3-none-any.whl" } ], "0.5.10": [ { "comment_text": "", "digests": { "md5": "bfb5b07ba6da5e00212ecd8b5d8252f0", "sha256": "1b51151ff8ff5af5ab9ff72440ef09b4ac1ffcab7406afd073704412653e01c1" }, "downloads": -1, "filename": "pypyr-0.5.10-py3-none-any.whl", "has_sig": false, "md5_digest": "bfb5b07ba6da5e00212ecd8b5d8252f0", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 55650, "upload_time": "2017-06-02T14:51:29", "url": "https://files.pythonhosted.org/packages/fd/9a/8ca7ead35c395f32f3e4150e6add5838fc5207eddd5a7c39d0b89c6e0894/pypyr-0.5.10-py3-none-any.whl" } ], "0.5.11": [ { "comment_text": "", "digests": { "md5": "28213c08c3b2e85f1ca08694ed21f92b", "sha256": "b7a480e2b258f1f8d2ad54d4b1eb71c0ee4bd7a568b17d22cfcf2cb831098c9e" }, "downloads": -1, "filename": "pypyr-0.5.11-py3-none-any.whl", "has_sig": false, "md5_digest": "28213c08c3b2e85f1ca08694ed21f92b", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 55677, "upload_time": "2017-06-05T07:44:52", "url": "https://files.pythonhosted.org/packages/69/f9/3cb4012b1a0cbdfb8250563d751b989bd4b88f8df85876f04565972260da/pypyr-0.5.11-py3-none-any.whl" } ], "0.5.12": [ { "comment_text": "", "digests": { "md5": "3ab0c91c4b0af746c8962edb5bdb89f0", "sha256": "e227cf927447080194481b676652f943e46680395aa0b318f5421a6238fd6289" }, "downloads": -1, "filename": "pypyr-0.5.12-py3-none-any.whl", "has_sig": false, "md5_digest": "3ab0c91c4b0af746c8962edb5bdb89f0", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 58158, "upload_time": "2017-06-08T09:28:53", "url": "https://files.pythonhosted.org/packages/33/07/543a1d488bb2d8d54203b830c4a1fde6dc819be7aa9de92a7159c9078ca3/pypyr-0.5.12-py3-none-any.whl" } ], "0.5.13": [ { "comment_text": "", "digests": { "md5": "4c9bdd8b6bf97b1c2e70a6b567dbf7d3", "sha256": "c7a6ce2ae53b79af356c291344454faa0efef3c16cc9690a076557a700ed8d5e" }, "downloads": -1, "filename": "pypyr-0.5.13-py3-none-any.whl", "has_sig": false, "md5_digest": "4c9bdd8b6bf97b1c2e70a6b567dbf7d3", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 59824, "upload_time": "2017-06-09T15:04:35", "url": "https://files.pythonhosted.org/packages/68/b7/0fa2794733362cb7cd46118f38185f329f99a0c41ce35a8624fb50fb2ccf/pypyr-0.5.13-py3-none-any.whl" } ], "0.5.2": [ { "comment_text": "", "digests": { "md5": "f05d656c202b8d88493ada85470a46fe", "sha256": "b22bc95a44eb005861be49a48fcf72ded3fc7b9440b5e3da59645731307bd9e1" }, "downloads": -1, "filename": "pypyr-0.5.2-py3-none-any.whl", "has_sig": false, "md5_digest": "f05d656c202b8d88493ada85470a46fe", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 43326, "upload_time": "2017-05-04T16:29:21", "url": "https://files.pythonhosted.org/packages/0c/40/33ee2d7adcc5875d74386fe12ff1fe3c7eaa3bf1596ea6f88b53e534c3e6/pypyr-0.5.2-py3-none-any.whl" } ], "0.5.3": [ { "comment_text": "", "digests": { "md5": "7e9af368429fb54b2bc6a5d62f5e273c", "sha256": "b2a6b819af96c613cb21ddf0d14594c4290cf79160c774fc65b12a02d811455f" }, "downloads": -1, "filename": "pypyr-0.5.3-py3-none-any.whl", "has_sig": false, "md5_digest": "7e9af368429fb54b2bc6a5d62f5e273c", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 45979, "upload_time": "2017-05-12T13:03:41", "url": "https://files.pythonhosted.org/packages/3a/70/2b77a99825ffe0e94110df8dec242478d8f4aa90d5345a88250f15afaa62/pypyr-0.5.3-py3-none-any.whl" } ], "0.5.4": [ { "comment_text": "", "digests": { "md5": "62baab38f0ee04811300cc1cba253c7e", "sha256": "5495243a6116e34aed1b92f1c6a486a36135f6710d20c64eeb5e583f1f025a82" }, "downloads": -1, "filename": "pypyr-0.5.4-py3-none-any.whl", "has_sig": false, "md5_digest": "62baab38f0ee04811300cc1cba253c7e", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 52914, "upload_time": "2017-05-15T10:47:54", "url": "https://files.pythonhosted.org/packages/96/9a/a79ba1d9a50307e09e72cf36bd5d066ef1d3462f617ea49e4c095b1ce431/pypyr-0.5.4-py3-none-any.whl" } ], "0.5.5": [ { "comment_text": "", "digests": { "md5": "17c2410cef3fa3cf670225c1f10872a7", "sha256": "6462e902fe1ff95707ffbc8a21f6ea9c4e568245afc6480d23e2c9fc03f32a8f" }, "downloads": -1, "filename": "pypyr-0.5.5-py3-none-any.whl", "has_sig": false, "md5_digest": "17c2410cef3fa3cf670225c1f10872a7", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 54393, "upload_time": "2017-05-26T14:58:42", "url": "https://files.pythonhosted.org/packages/e2/a1/9588de3ccfe8473857abe3fcfd97072945098d77aa314af8b5efb5e0f222/pypyr-0.5.5-py3-none-any.whl" } ], "0.5.6": [ { "comment_text": "", "digests": { "md5": "e3f94df641346b81ada09f2654c5d81a", "sha256": "e9d06bbd481c123bd47c73ac3dfe4e2429e7224926fd0270a37807a4c694250e" }, "downloads": -1, "filename": "pypyr-0.5.6-py3-none-any.whl", "has_sig": false, "md5_digest": "e3f94df641346b81ada09f2654c5d81a", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 54628, "upload_time": "2017-05-30T10:19:25", "url": "https://files.pythonhosted.org/packages/3a/35/cb37d71ec118c68838482284a6fcfc40b5bde1449f9cff9a30296afcff32/pypyr-0.5.6-py3-none-any.whl" } ], "0.5.7": [ { "comment_text": "", "digests": { "md5": "6d2780e690a6659f93f1554850ae61da", "sha256": "69e8cff6857ce1595cb5972d92a733544c82d55ce53ea6a1c255b57cdef8aa48" }, "downloads": -1, "filename": "pypyr-0.5.7-py3-none-any.whl", "has_sig": false, "md5_digest": "6d2780e690a6659f93f1554850ae61da", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 54708, "upload_time": "2017-06-01T14:47:36", "url": "https://files.pythonhosted.org/packages/bb/ff/1c19dc1da28b20e44d987d883fab39a7d237fc940401f5c9fea5608b1926/pypyr-0.5.7-py3-none-any.whl" } ], "0.5.8": [ { "comment_text": "", "digests": { "md5": "66b47503e7f3f40a9225fe2d34c16eda", "sha256": "585dcf3ff203a34ed894acf7a32d57d59c300ac388983315f60d8b6fc503ec24" }, "downloads": -1, "filename": "pypyr-0.5.8-py3-none-any.whl", "has_sig": false, "md5_digest": "66b47503e7f3f40a9225fe2d34c16eda", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 54708, "upload_time": "2017-06-01T16:21:56", "url": "https://files.pythonhosted.org/packages/8e/04/11bf206f29f396e45bcf4f8789c80fcd42d3672fac2aabf6eaf39122acbe/pypyr-0.5.8-py3-none-any.whl" } ], "0.5.9": [ { "comment_text": "", "digests": { "md5": "6ec7039f5887dd090993f2c228c2d0d5", "sha256": "cc100e53f79a11737569e7056b2d18274e647bb93dc209ca2281ec46aed71588" }, "downloads": -1, "filename": "pypyr-0.5.9-py3-none-any.whl", "has_sig": false, "md5_digest": "6ec7039f5887dd090993f2c228c2d0d5", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 55635, "upload_time": "2017-06-01T17:13:09", "url": "https://files.pythonhosted.org/packages/56/5c/3ef14b0ad2efe02f60b50708762a654665ee0926d3876d51cf28aa07a2d3/pypyr-0.5.9-py3-none-any.whl" } ], "0.6.0": [ { "comment_text": "", "digests": { "md5": "3cd4cd915aff2eb66e4f7dd568de3171", "sha256": "ac68fdf8844530c70de1174c098075ea44b29c20a5def3a90e5ac4712cb9c059" }, "downloads": -1, "filename": "pypyr-0.6.0-py3-none-any.whl", "has_sig": false, "md5_digest": "3cd4cd915aff2eb66e4f7dd568de3171", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 65359, "upload_time": "2017-06-12T09:58:08", "url": "https://files.pythonhosted.org/packages/ff/54/0e6cf3729efe08b9b8d3ea98c7bcc28097a1b4a17dd99b6ff3cc37e2ea5c/pypyr-0.6.0-py3-none-any.whl" } ], "0.6.1": [ { "comment_text": "", "digests": { "md5": "691787185975d5ebf7694c8823105011", "sha256": "e6aaa0da430db00cc6522e767be623cb9cb941fcca1acf94901176f6ed071761" }, "downloads": -1, "filename": "pypyr-0.6.1-py3-none-any.whl", "has_sig": false, "md5_digest": "691787185975d5ebf7694c8823105011", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 65402, "upload_time": "2017-06-12T14:27:27", "url": "https://files.pythonhosted.org/packages/6b/46/710d519924cf7be18474cba33636f2cc139fb46c99ac9abaab4b51e1efdb/pypyr-0.6.1-py3-none-any.whl" } ], "0.7.0": [ { "comment_text": "", "digests": { "md5": "8ca5145380e06fa042f08956b3b16838", "sha256": "bde111aace4b25080bd3dfa5201fc3889e6c52e0e86dcfcf5bbdc59dca269783" }, "downloads": -1, "filename": "pypyr-0.7.0-py3-none-any.whl", "has_sig": false, "md5_digest": "8ca5145380e06fa042f08956b3b16838", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 62130, "upload_time": "2018-06-13T23:04:34", "url": "https://files.pythonhosted.org/packages/06/c0/4d88d988ada0a9d37732caa2853b04d3d4b6aeca7cde3a8a8ff8811fea2f/pypyr-0.7.0-py3-none-any.whl" } ], "0.7.1": [ { "comment_text": "", "digests": { "md5": "46cf51e9ddbaf9060953fdb1c7bf9728", "sha256": "af19e459b71c3abff3cf9606246e939d8951551bfef8ee4ebff63160f91e8dd4" }, "downloads": -1, "filename": "pypyr-0.7.1-py3-none-any.whl", "has_sig": false, "md5_digest": "46cf51e9ddbaf9060953fdb1c7bf9728", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 51488, "upload_time": "2018-06-14T01:57:47", "url": "https://files.pythonhosted.org/packages/c4/a6/6306be9dd42ac1322c3511ec6773454783c84784b1ee9a3e145bb07a4525/pypyr-0.7.1-py3-none-any.whl" } ], "0.8.0": [ { "comment_text": "", "digests": { "md5": "a2ad1b3dcaaa6ab0e8837fa905ace073", "sha256": "6b5b76f2db7c13effa2fbae4b04b690a1eb836ad0bd1e9b0d73b2b7398709fe9" }, "downloads": -1, "filename": "pypyr-0.8.0-py3-none-any.whl", "has_sig": false, "md5_digest": "a2ad1b3dcaaa6ab0e8837fa905ace073", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 52512, "upload_time": "2018-06-16T20:10:35", "url": "https://files.pythonhosted.org/packages/08/b9/f13bb09838ff9f1ef30fd34f1471636209080781ad57a408ce682c35e6a7/pypyr-0.8.0-py3-none-any.whl" } ], "0.8.1": [ { "comment_text": "", "digests": { "md5": "070b599aba78fa1a1c5e9b2e1fc0b636", "sha256": "2d1c4b228e4dd8091b16c909fbb79688660eceac2b3004d52a56deb0dd2ef87b" }, "downloads": -1, "filename": "pypyr-0.8.1-py3-none-any.whl", "has_sig": false, "md5_digest": "070b599aba78fa1a1c5e9b2e1fc0b636", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 55531, "upload_time": "2018-07-06T18:12:39", "url": "https://files.pythonhosted.org/packages/d9/83/44d2bce005818a89a0a05a3f584354f1ecbf00d7b6e77ba0ff99981d7596/pypyr-0.8.1-py3-none-any.whl" } ], "0.8.2": [ { "comment_text": "", "digests": { "md5": "cb2ae5871104def94a024f024fb7fad0", "sha256": "b4bb7d70c4f65c16a40e676e5cc6e00ed61651e38eae1c151dd9c2f1fdc55366" }, "downloads": -1, "filename": "pypyr-0.8.2-py3-none-any.whl", "has_sig": false, "md5_digest": "cb2ae5871104def94a024f024fb7fad0", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 56223, "upload_time": "2018-07-08T17:15:07", "url": "https://files.pythonhosted.org/packages/f7/97/0c40c9cc09f474140a26aead84266a36b1c1b2c5f880a7d8540ecc4eb03f/pypyr-0.8.2-py3-none-any.whl" } ], "0.9.0": [ { "comment_text": "", "digests": { "md5": "12d3c043b55e420b7057b10416d5ebdd", "sha256": "5b25151593a32e91be8126208f9602810ecb6a1d752db434e5576fe8ab122dd3" }, "downloads": -1, "filename": "pypyr-0.9.0-py3-none-any.whl", "has_sig": false, "md5_digest": "12d3c043b55e420b7057b10416d5ebdd", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": null, "size": 57284, "upload_time": "2018-07-09T20:02:12", "url": "https://files.pythonhosted.org/packages/93/45/9a56c10e7d8c8420ab14057416a066b83e029b2257d4c728b70d648e081c/pypyr-0.9.0-py3-none-any.whl" } ], "1.0.0": [ { "comment_text": "", "digests": { "md5": "b08ccc1db3cbd0904f4d8f1e19555b39", "sha256": "8a4f9bf0d495117018c0630b0b1e796f615d0f6f9b7b28a068797239f853618f" }, "downloads": -1, "filename": "pypyr-1.0.0-py3-none-any.whl", "has_sig": false, "md5_digest": "b08ccc1db3cbd0904f4d8f1e19555b39", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 64797, "upload_time": "2018-07-26T20:18:18", "url": "https://files.pythonhosted.org/packages/ba/38/2b05a9d668efcf36a9e37ef13c88d5bb6c6197cbe65348c2b1f4f9f77191/pypyr-1.0.0-py3-none-any.whl" } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "1a22cd7c0193586fb776a77afcca3c0a", "sha256": "4c69f50bb6f9faa419b42e2c7a57c81e6ad6abe21a5d30efebd9b5592885923b" }, "downloads": -1, "filename": "pypyr-1.1.1-py3-none-any.whl", "has_sig": false, "md5_digest": "1a22cd7c0193586fb776a77afcca3c0a", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 65378, "upload_time": "2018-10-18T22:44:46", "url": "https://files.pythonhosted.org/packages/fc/58/373445b60695848a6084bac7e9363aee62b21181329ef91a4b5eb6fdfdf5/pypyr-1.1.1-py3-none-any.whl" } ], "1.2.0": [ { "comment_text": "", "digests": { "md5": "f91c63a31e7b47c5dca2d6460d7b1321", "sha256": "8ad61856bd177ce1a8b0add64ff79b2c1ff6aa0ed8640184d96002f4fb75304a" }, "downloads": -1, "filename": "pypyr-1.2.0-py3-none-any.whl", "has_sig": false, "md5_digest": "f91c63a31e7b47c5dca2d6460d7b1321", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 65494, "upload_time": "2018-11-05T09:27:36", "url": "https://files.pythonhosted.org/packages/4a/46/09cff0b6cb9dc83be05af5812dd19b6ef971a9df09cede7dd45e68e56ec8/pypyr-1.2.0-py3-none-any.whl" } ], "1.2.1": [ { "comment_text": "", "digests": { "md5": "baf2cf061b7f7718b094a01917d84060", "sha256": "22288c41b27487b5f05f9a66755ef6e77acc507d5ea4551300600c2881452293" }, "downloads": -1, "filename": "pypyr-1.2.1-py3-none-any.whl", "has_sig": false, "md5_digest": "baf2cf061b7f7718b094a01917d84060", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 65495, "upload_time": "2018-11-05T11:09:09", "url": "https://files.pythonhosted.org/packages/26/47/6b8851e4aaa99b0b1dc90294defff390f2ea88080f57cb5a3803f5c6fcff/pypyr-1.2.1-py3-none-any.whl" } ], "2.0.0": [ { "comment_text": "", "digests": { "md5": "eceeff0b3d7269fcbb5a8b66c0f035c5", "sha256": "377cfd15415f927fdaddd27e97c9ad338f3443988c308ea03596e913158d6977" }, "downloads": -1, "filename": "pypyr-2.0.0-py3-none-any.whl", "has_sig": false, "md5_digest": "eceeff0b3d7269fcbb5a8b66c0f035c5", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 73186, "upload_time": "2018-12-16T20:21:49", "url": "https://files.pythonhosted.org/packages/b6/d6/1118724972b1d7ab703e9712a36b93360ac5d4be13866a7aae7944908940/pypyr-2.0.0-py3-none-any.whl" } ], "2.1.0": [ { "comment_text": "", "digests": { "md5": "50bae2e50eb7b6d098997998374c4022", "sha256": "5173588a6339cba0ebd1b1af93eb075ee85f9c4160a92c39ca91c8a2876a53ef" }, "downloads": -1, "filename": "pypyr-2.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "50bae2e50eb7b6d098997998374c4022", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 89482, "upload_time": "2019-01-03T00:56:32", "url": "https://files.pythonhosted.org/packages/07/23/c501008ee024ca14b33221fdeb159a2364b0e6c94a9d397bb857d3969bc4/pypyr-2.1.0-py3-none-any.whl" } ], "2.1.1": [ { "comment_text": "", "digests": { "md5": "e8a12ba07a20051265af9be6b4979473", "sha256": "77eb4f1027de5271edc9fe97a882058b99708b249fb55e7d91e1cc9618afc3cf" }, "downloads": -1, "filename": "pypyr-2.1.1-py3-none-any.whl", "has_sig": false, "md5_digest": "e8a12ba07a20051265af9be6b4979473", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 89537, "upload_time": "2019-01-04T11:36:35", "url": "https://files.pythonhosted.org/packages/e7/02/b45169390d46a60c165ddcbb5601bc755e42d77300ac9e73cbea4bf117c1/pypyr-2.1.1-py3-none-any.whl" } ], "2.10.0": [ { "comment_text": "", "digests": { "md5": "3ba29174951b0f71b5294b9f5d77f1c3", "sha256": "f8abb3895b921fd609c1921f74a022da1322c7e7448ab54afe656491786476cb" }, "downloads": -1, "filename": "pypyr-2.10.0-py3-none-any.whl", "has_sig": false, "md5_digest": "3ba29174951b0f71b5294b9f5d77f1c3", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 103615, "upload_time": "2019-07-10T08:50:06", "url": "https://files.pythonhosted.org/packages/cd/6e/6003f6ce66db06655e806030c4f1a4ce5ffb79ed86f6108e89315499c336/pypyr-2.10.0-py3-none-any.whl" } ], "2.11.0": [ { "comment_text": "", "digests": { "md5": "10633a833f78b9598239d1aa6c4ed60a", "sha256": "7809bda9940c2ab71b235cf5d4739a608a4fb40e1fa1594a2c023e5f0e83451d" }, "downloads": -1, "filename": "pypyr-2.11.0-py3-none-any.whl", "has_sig": false, "md5_digest": "10633a833f78b9598239d1aa6c4ed60a", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 105361, "upload_time": "2019-08-06T21:18:22", "url": "https://files.pythonhosted.org/packages/b7/ae/799ef9434b59a84153b7f2c50e79ac3f54eb289eb55ee2570fcbb587b72c/pypyr-2.11.0-py3-none-any.whl" } ], "2.2.0": [ { "comment_text": "", "digests": { "md5": "37c2e54a2c758906df35b1512349bf2f", "sha256": "ba79e2b4d09e8b7e51091406a74028414cb0caeaf6d38ace39ad7509ac39c462" }, "downloads": -1, "filename": "pypyr-2.2.0-py3-none-any.whl", "has_sig": false, "md5_digest": "37c2e54a2c758906df35b1512349bf2f", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 91222, "upload_time": "2019-01-18T10:36:05", "url": "https://files.pythonhosted.org/packages/84/44/e38c8fa2d00132e27bddf026f9a9134184271384392742ecd1e8d4765b4f/pypyr-2.2.0-py3-none-any.whl" } ], "2.3.0": [ { "comment_text": "", "digests": { "md5": "87bef09789f21919702188a696f5ced8", "sha256": "ce285e18964a5e75652c549957f87e011c9aacd4a2526f0db60db48c3855ce2c" }, "downloads": -1, "filename": "pypyr-2.3.0-py3-none-any.whl", "has_sig": false, "md5_digest": "87bef09789f21919702188a696f5ced8", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 93583, "upload_time": "2019-01-24T02:20:25", "url": "https://files.pythonhosted.org/packages/c7/c3/dc5ecd4c22707a5dd6b9fae2ff95644dc7614d26dc6d5129f0567372eca8/pypyr-2.3.0-py3-none-any.whl" } ], "2.4.0": [ { "comment_text": "", "digests": { "md5": "b32eb44519866e905917583111d8db08", "sha256": "7ef9189b23cd8e616ce4e34ff657edd4efdd400f2935db96e9cb5c19ae0e1528" }, "downloads": -1, "filename": "pypyr-2.4.0-py3-none-any.whl", "has_sig": false, "md5_digest": "b32eb44519866e905917583111d8db08", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 96361, "upload_time": "2019-01-24T23:06:46", "url": "https://files.pythonhosted.org/packages/8a/b4/7b4e8b3407eb5f3aa724c0f9a6e0126a6369455c07d867e6157af20e8110/pypyr-2.4.0-py3-none-any.whl" } ], "2.5.0": [ { "comment_text": "", "digests": { "md5": "cf58a3069357a35e8296267a229a5448", "sha256": "f716e6cfc2f683a7850b1833b6517ea83e55ad9d01f20637a781bf2fd37984c0" }, "downloads": -1, "filename": "pypyr-2.5.0-py3-none-any.whl", "has_sig": false, "md5_digest": "cf58a3069357a35e8296267a229a5448", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 96939, "upload_time": "2019-01-30T19:10:27", "url": "https://files.pythonhosted.org/packages/51/4f/7d7c047c4f35d9088eab68de655a5ed289fa8501f4607124e933447c9ccb/pypyr-2.5.0-py3-none-any.whl" } ], "2.6.0": [ { "comment_text": "", "digests": { "md5": "8209a14ce83cf81eef2388d811880c21", "sha256": "a3f291dcec84d342b9a41060d85c46876f66d13877fe0828c28ab5230318745d" }, "downloads": -1, "filename": "pypyr-2.6.0-py3-none-any.whl", "has_sig": false, "md5_digest": "8209a14ce83cf81eef2388d811880c21", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 99190, "upload_time": "2019-02-08T16:21:58", "url": "https://files.pythonhosted.org/packages/b0/8a/a0f8bc3ead41b803005217ce28a60dc488b57214b2e4ab7384661056c11f/pypyr-2.6.0-py3-none-any.whl" } ], "2.7.0": [ { "comment_text": "", "digests": { "md5": "ed0084b233555618c7a4731d285500b7", "sha256": "39eef1b7247da5d17d7fed647a1f21ecadf6592d8c182070034605e7d97a8fab" }, "downloads": -1, "filename": "pypyr-2.7.0-py3-none-any.whl", "has_sig": false, "md5_digest": "ed0084b233555618c7a4731d285500b7", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 99201, "upload_time": "2019-03-21T15:10:55", "url": "https://files.pythonhosted.org/packages/98/7c/7c8f19e853814cfdb65b9da7229f2b8a1e97c9d94eae65d760c09347ee9e/pypyr-2.7.0-py3-none-any.whl" } ], "2.8.0": [ { "comment_text": "", "digests": { "md5": "fd9c38bf8ad527a4737a2d40abc5b2d8", "sha256": "ab27a36982274a834a1ab4ce655eab003fe76efdc09ca421981a845414694416" }, "downloads": -1, "filename": "pypyr-2.8.0-py3-none-any.whl", "has_sig": false, "md5_digest": "fd9c38bf8ad527a4737a2d40abc5b2d8", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 100986, "upload_time": "2019-03-24T01:23:29", "url": "https://files.pythonhosted.org/packages/7f/ae/5cd8cbcbe484f4199e7f5c0a2895b650ee65bc6c8916a881c5edbea40d9e/pypyr-2.8.0-py3-none-any.whl" } ], "2.9.0": [ { "comment_text": "", "digests": { "md5": "e17bef8199debcd08a6c64d5559b709c", "sha256": "b45c004f2827cc7c941c6f73a5bd0a321a8ade7da56ad342cfd969c6af655dbb" }, "downloads": -1, "filename": "pypyr-2.9.0-py3-none-any.whl", "has_sig": false, "md5_digest": "e17bef8199debcd08a6c64d5559b709c", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 102318, "upload_time": "2019-06-14T18:52:57", "url": "https://files.pythonhosted.org/packages/92/bc/9fa419e2cc8da08151eb59823509e2aca94e48a9d183fa620e365cc76154/pypyr-2.9.0-py3-none-any.whl" } ], "3.0.0": [ { "comment_text": "", "digests": { "md5": "15b555d44fa309f90474dc6f918becce", "sha256": "95caf695c68eca43ecea7b1e352cfecd0929a2829f3cadfaa0496835a9d16f4c" }, "downloads": -1, "filename": "pypyr-3.0.0-py3-none-any.whl", "has_sig": false, "md5_digest": "15b555d44fa309f90474dc6f918becce", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 122065, "upload_time": "2019-08-22T18:12:37", "url": "https://files.pythonhosted.org/packages/e5/03/a11284af78f3e82a7301b95f184b48bd81c7eb80f26807073e2eb54ce475/pypyr-3.0.0-py3-none-any.whl" } ], "3.0.1": [ { "comment_text": "", "digests": { "md5": "10e4c820868450bef4cd720a32f66bdf", "sha256": "b61f6ab922755b4b90c0ebae2023e7af5eeac2559507349891062e86020a735e" }, "downloads": -1, "filename": "pypyr-3.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "10e4c820868450bef4cd720a32f66bdf", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 117967, "upload_time": "2019-09-13T20:12:48", "url": "https://files.pythonhosted.org/packages/cc/3c/ba92ce3f69fd4d89c969a6ecde30d4628ec96f135538e6abb9615c764e27/pypyr-3.0.1-py3-none-any.whl" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "10e4c820868450bef4cd720a32f66bdf", "sha256": "b61f6ab922755b4b90c0ebae2023e7af5eeac2559507349891062e86020a735e" }, "downloads": -1, "filename": "pypyr-3.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "10e4c820868450bef4cd720a32f66bdf", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 117967, "upload_time": "2019-09-13T20:12:48", "url": "https://files.pythonhosted.org/packages/cc/3c/ba92ce3f69fd4d89c969a6ecde30d4628ec96f135538e6abb9615c764e27/pypyr-3.0.1-py3-none-any.whl" } ] }