yaml_requests
A simple python app for sending a set of consecutive HTTP requests defined in YAML requests plan.
Installing
Ensure that you are using Python >= 3.9 with python --version. This app/package is available in PyPI. To install, run:
pip install yaml_requests
Usage
The app is used to execute HTTP requests defined in YAML files. The YAML file must contain main-level key requests, that contains an array of requests, where each item of the list is a request object. The request object contains at least a method key (get, post, options, ...) which value is passed to requests.request function, or to requests.Session.request if plan level option session is truthy.
Minimal YAML request plan should thus include requests array, with single item in it:
requests:
- get:
url: https://google.com
In addition to this basic behavior, more advanced features are provided as well:
- All value fields in requests array items support jinja2 templating.
- Values can be read from environment variables with
lookupfunction. For example,{{ lookup("env", "API_TOKEN") }}. - Files can be read as text with
lookupfunction (e.g.,{{ lookup("file", "headers.yaml")}}) or opened withopenfunction (e.g.{{ open("photos/eiffer-tower.jpg") }}) to pass in as file objects tofilesparameter of request functions. - Variables can be defined in YAML request plan and overridden from commandline arguments.
- Response of the most recent request is stored in
responsevariable asrequests.Responseobject. - Responses can be stored as variables with
registerkeyword. - Response can be verified with assertions.
- Plan execution can be repeated by setting
repeat_whileoption. - Request can be looped by defining
loopoption for a request. The current item is available initemvariable.
API Reference
1""" 2.. include:: ../README.md 3 :start-after: <!-- Start docs include --> 4 :end-before: <!-- End docs include --> 5 6## API Reference 7""" 8 9from importlib.metadata import version 10__version__ = version('yaml_requests') 11 12from ._main import execute, main, run 13from ._plan import Plan, PlanOptions 14from ._request import Assertion, Request 15 16 17# Hide dataclass constructors from documentation. 18for i in (Plan, PlanOptions, Assertion, Request): 19 i.__init__.__doc__ = '@private' 20 21 22__all__ = [ 23 'error', 24 'Plan', 25 'PlanOptions', 26 'Request', 27 'Assertion', 28]
88@dataclass 89class Plan: 90 '''A plan that contains requests to be executed consecutively.''' 91 92 name: str 93 '''Human readable description for the plan.''' 94 path: str 95 '''@private Path to the plan file.''' 96 options: PlanOptions 97 '''Options for controlling the execution of the plan. See `PlanOptions` 98 for details.''' 99 variable_files: list[str] 100 '''List of paths to variable files. The variable file can be relative to 101 either the current working directory or the plan file. The variable file 102 must be a JSON or YAML file. 103 104 See `variables` for variable precedence. 105 ''' 106 variables: dict 107 '''Variables to be used in the plan. 108 109 Variable precedence from least to greatest: 110 111 1. Variables defined in the `variables` field of the plan. 112 2. Variables defined in the variable files. 113 3. Variables defined in the shell command with `-v`/`--variable` option. 114 ''' 115 requests: list[Request] 116 '''List of requests to be executed.''' 117 118 @classmethod 119 def _from_dict( 120 cls, 121 input_dict, 122 options_override=None, 123 variables_override=None): 124 if variables_override is None: 125 variables_override = {} 126 127 plan_dict = deepcopy(input_dict) 128 129 path = plan_dict.pop('path', None) 130 variable_files = _resolve_variable_files( 131 plan_dict.get('variable_files'), path) 132 variables = { 133 **plan_dict.get('variables', {}), 134 **_load_variable_files(variable_files), 135 **variables_override 136 } 137 138 requests = plan_dict.get('requests') 139 if not requests or not isinstance(requests, list): 140 raise AssertionError('Plan must contain requests array.') 141 142 return cls( 143 name=plan_dict.pop('name', None), 144 path=path, 145 options=PlanOptions._from_dict( 146 plan_dict.get('options'), options_override), 147 variable_files=variable_files, 148 variables=variables, 149 requests=requests 150 ) 151 152 def _title(self, display_filename=False): 153 if not display_filename: 154 return self.name 155 156 if self.name and self.path: 157 return f'{self.name} ({self.path})' 158 159 return self.path or self.name
A plan that contains requests to be executed consecutively.
Options for controlling the execution of the plan. See PlanOptions
for details.
List of paths to variable files. The variable file can be relative to either the current working directory or the plan file. The variable file must be a JSON or YAML file.
See variables for variable precedence.
Variables to be used in the plan.
Variable precedence from least to greatest:
- Variables defined in the
variablesfield of the plan. - Variables defined in the variable files.
- Variables defined in the shell command with
-v/--variableoption.
17@dataclass 18class PlanOptions: 19 '''Options for controlling the execution of the plan.''' 20 21 session: bool = False 22 '''Use session to keep cookies between requests.''' 23 ignore_errors: bool = None 24 '''Continue executing requests even if one of them fails.''' 25 repeat_while: str = None 26 '''Expression that determines if the plan should be repeated.''' 27 repeat_delay: int = None 28 '''Time to sleep in seconds before repeating the plan.''' 29 30 @classmethod 31 def _from_dict(cls, options_dict=None, options_override=None): 32 if options_dict is None: 33 options_dict = {} 34 35 if options_override is None: 36 options_override = {} 37 38 return cls(**{**options_dict, **options_override})
Options for controlling the execution of the plan.
116@dataclass 117class Request: 118 '''A Request to send.''' 119 120 name: str 121 '''Human readable description for the request.''' 122 method: str 123 '''HTTP method to use. 124 125 Can also be defined as a main level dict key with `params` as the value. 126 For example: 127 128 ```yaml 129 - name: Get index page 130 get: 131 url: http://localhost:8080 132 ```''' 133 params: dict 134 '''Parameters to pass to the `requests.request` function. 135 136 Can also be defined as a main level dict value with the HTTP `method` as 137 the key. See `method` for details.''' 138 loop: str 139 '''Loop over the given list of items.''' 140 assertions: list[Union[Assertion, str]] 141 '''List of assertions to execute after the request is sent. 142 143 Can also be defined with `assert` key.''' 144 register: str = None 145 '''Register the response object as a variable with the given name.''' 146 raise_for_status: bool = True 147 '''Raise an exception if the response status code is not ok.''' 148 output: str = None 149 '''Output the given properties of the response, e.g. `response_json`.''' 150 151 def _parse_options(self, request_dict): 152 self.register = request_dict.get('register') 153 self.raise_for_status = request_dict.get('raise_for_status', True) 154 self.output = request_dict.get('output')
A Request to send.
HTTP method to use.
Can also be defined as a main level dict key with params as the value.
For example:
- name: Get index page
get:
url: http://localhost:8080
List of assertions to execute after the request is sent.
Can also be defined with assert key.
61@dataclass 62class Assertion: 63 '''An assertion to execute after the request is sent. The assertion is 64 considered successful if the expression evaluates to `True`. 65 66 For example: 67 68 ```yaml 69 - name: Response is not empty 70 expression: response.json() | length 71 ``` 72 ''' 73 74 name: str 75 '''Human readable description for the assertion.''' 76 expression: str 77 '''Expression to evaluate.'''
An assertion to execute after the request is sent. The assertion is
considered successful if the expression evaluates to True.
For example:
- name: Response is not empty
expression: response.json() | length