asyncio-requests 2.7.3
Any microservice will be able to use the “asyncio_requests” can make an async request(HTTP/SOAP/XML/FTP/redis) with the given payload to given address
License: MIT

# Async HTTP / SOAP / FTP Request Library

This library provides the functionality to make async API calls via HTTP / SOAP / FTP protocols via a config.

### Installation
```pip install asyncio-requests ```

## HTTP

* Uses aiohttp internally
* Has an inbuilt circuit breaker
* Currently supports infinite nested depth of pre and post processors
* Retry Functionality
* Exceptions can be contributed in the utilities, and you can use your own exceptions in the circuit breaker config as well.
* Direct File Upload functionality.

Params -

<table>
  <tbody>
    <tr>
      <th class="Title" align="center">Param</th>
      <th class="Title" align="center">Data Type</th>
      <th class="Title" align="center">Optional/Required</th>
      <th class="Title" align="center">Help</th>
    </tr>
    <tr>
      <td align="center">url</td>
      <td align="center">Str</td>
      <td align="center">Required</td>
      <td align="center">URL to be hit</td>
    </tr>
    <tr>
      <td align="center">data</td>
      <td align="center">Dict</td>
      <td align="center">Optional</td>
      <td align="center">data to be sent. It can be dict or str. If dict, it will be dumped via ujson.dumps method</td>
    </tr>
    <tr>
      <td align="center">auth</td>
      <td align="center">auth object</td>
      <td align="center">Optional</td>
      <td align="center">Auth param is expected to be an auth object of your choice which is accepted by aiohttp. Eg - aiohttp.BasicAuth(username, password)</td>
    </tr>
    <tr>
      <td align="center">protocol</td>
      <td align="center">Str</td>
      <td align="center">Required</td>
      <td align="center">(HTTP/HTTPS/SOAP/FTP)</td>
    </tr>
    <tr>
      <td align="center">pre_processor_config</td>
      <td align="center">Dict</td>
      <td align="center">Optional</td>
      <td>
        <ul>
          <li>pre processor indicates an action (file download/api call or anything) to be done before making the actual API call.</li>
          <li>Takes async callable object which is executed before making the actual API call - Required</li>
          <li>Params dictionary where key is parameter to the callable object passed in pre processor and values is parameter value</li>
          <li>The callable object/function can be used from the utilities folder which is contributed by all or your own function address.</li>
          <li>You can nest the whole API. Eg - you can pass the address of asyncio_requests.request function too. The response will be a nested one. (Explained via example down)</li>
        </ul>
      </td>
    </tr>
    <tr>
      <td align="center">post_processor_config</td>
      <td align="center">Dict</td>
      <td align="center">Optional</td>
      <td>
        <ul>
          <li>post processor indicates an action (file download or delete file or api call or anything) to be done post making the actual API call.</li>
          <li>function: Takes async callable object/function address which is executed after making request - Required</li>
          <li>Params: Takes dictionary where key is parameter to the callable object/function passed in pre-processor and values is parameter value</li>
          <li>similar to pre-processor, difference being this is executed after making an API call.</li>
          <li>Eg - if you want to send the data of a file in the API call and the file needs to be downloaded. You can have a file download pre-processor function and have a file deletion post processor function.</li>
        </ul>
      </td>
    </tr>
    <tr aria-rowspan="12">
      <td align="center">protocol_info</td>
      <td align="center">Dict</td>
      <td align="center">Required</td>
      <td>
        <ul>
          <li>request_type - Str. Required. GET/PUT/POST/PATCH/DELETE/OPTIONS</li>
          <li>timeout -      Int. Optional. Default HTTP timeout is 15 seconds. Can be overridden if specified.</li>
          <li>certificate -  Tuple(str, str)  Optional. Used for SSL certificates and expected in the format Tuple('certificate path', 'certificate key path')</li>
          <li>verify_ssl -   Boolean. Optional. SSL is enabled by default</li>
          <li>cookies -      Str. Optional</li>
          <li>headers -      Dict. Required</li>
          <li>trace_config - List[tracer_callable_object] Optional. default tracer is aiohttp.TraceConfig() - Optional</li>
          <li>
            http_file_upload_config: Dict use this only if you want to send file in request. If you use this config then only file will be sent in request - Optional
            <ul>
              <li>local_filepath: machine file path for file to be sent in request</li>
              <li>file_key: The key in which the file data is to be sent</li>
            </ul>
          </li>serialization: serializer callable object. Optional. If you want to use any json serializer then you can pass here default is ujson.dumps.</li>
          <li>circuit_breaker_config - Dict - Optional
            <ul>
              <li>maximum_failures - Int. Optional. maximum failures you want to allow for request default is 5</li>
              <li>timeout - Int Optional. seconds timeout you want to keep for request default is 60 seconds</li>
              <li>retry_config - Dict - Optional
                <ul>
                  <li>name - Str required</li>
                  <li>allowed_retries - Int. Required this is for how many retries you want to perform</li>
                  <li>retriable_exceptions - List[<callable object of exception>]. Optional. list of exception types indicating which exceptions can cause a retry. If None every exception is considered retriable</li>
                  <li>abortable_exceptions - List[<callable object of exception>]. Optional. list of exception types indicating which exceptions should abort failsafe run immediately and be propagated out of failsafe. If None, no exception is considered abortable.</li>
                  <li>on_retries_exhausted - callable object. Optional. callable/function_address which will be invoked on retry exhausted event</li>
                  <li>on_failed_attempt - callable object. Optional. callable/function_address that will be invoked on a failed attempt event</li>
                  <li>on_abort - callable object. Optional. callable that will be invoked on an abort event</li>
                  <li>delay - Int Optional. seconds of delay between retries default is 0.</li> 
                  <li>max_delay - Int Optional. seconds of max delay between retries default 0</li>
                  <li>jitter: Boolean Optional. False when you want to keep the wait between calls constant else True</li>
                </ul>
              </li>
            </ul>
          </li>
        </ul>
      </td>
    </tr>
  </tbody>
</table>

Defaults - 
* By default, circuit breaker is not enabled and is activated only if provided with its config.
* By default, retry is not enabled and is activated only if provided with its config.
* Default Request tracer is enabled which provides the traces of the whole request wrt data chunks, dns cache hit etc.
* In case of user specific request tracer, a list of request tracer objects is expected which will override the default tracer.
* Default serialization is via ujson and can be overwritten by specifying one
* SSL is enabled by default

 

### How to Use
* Design the http request payload as per below format
* Mock url - https://api.fyndx1.de/masquerader/v1/aio-request-test/post is live and open for use.

```python
import aiohttp
from asyncio_requests.asyncio_request import request

await request(
    url="URL FOR REQUEST",  # str <Required>
    data={
        "key": "val"
    } or "",  # Data to be sent in body as dict or str,
    auth=aiohttp.BasicAuth('username', 'password'),  # This auth object is to be made by the user itself as there are n number of
    # auth mechanisms to add to. Eg - auth=aiohttp.BasicAuth(username, password). Its an Optional field.
    protocol="REQUEST PROTOCOL",  # str <Required> (HTTP/HTTPS)
    protocol_info={
        "request_type": "GET",  # str <Required>
        "timeout": 15,  # int <Optional> Default - 15
        "certificate": ('', ''),  # Tuple(str, str) <Optional>,
        "verify_ssl": True,  # Boolean <Optional>,
        "cookies": "",  # str <Optional>,
        "headers": {},  # dict <Optional>,
        "http_file_upload_config": {
            # optional Include only if you want call api with file. If this is included api body will have only file
            "local_filepath": "required",  # File path to be sent
            "file_key": "required",  # File to be sent on which key in request body
            "file_upload_chunk_size": "optional" # size of stream if streaming upload is required
            # After making API if you want to delete file then add value as True default is false.
        },
        "http_file_download_config": {
          "download_filepath": "required" # In case file downloads, location to which file is stored
          "file_download_chunk_size": "optional" # chunk size of a stream.
        }
        "circuit_breaker_config": {  # Optional
            "maximum_failures": int,  # Optional Failures allowed
            "timeout": int,  # Optional time in seconds
            "retry_config": {  # Optional Include this if you want retry API calls if failed on first time
                "name": str,  # Required Any name
                "allowed_retries": int,  # Required number of retries you want to make 
                "retriable_exceptions": [<callable object>] # Optional
                "abortable_exceptions": [<callable object>] # Optional
                "on_retries_exhausted": <callable object>, # Optional callable that will be invoked on a retries exhausted event,
                "on_failed_attempt": <callable object>, # Optional callable that will be invoked on a failed attempt event,
                "on_abort": <callable object>, # Optional callable that will be invoked on an abort event,
            "delay": int, # seconds of delay between retries Optional default 0,
            "max_delay": int, # seconds of max delay between retries Optional default 0,
        "jitter": bool # Boolean Optional,
            }
        }
    },
    pre_processor_config = {  # Optional
        "function": <callable object>,  # Required function that you want to call before http call
        "params": {  # Optional
            "param1": "value1" # Params you want to pass in function
        }
    },
    post_processor_config = {  # Optional
        "function": <callable object>,  # Required function that you want to call after http call 
        "params": {
            "param1": "value1" # Params you want to pass in function
        }
    }
)
```

* **Basic HTTP POST call**
```python
from asyncio_requests.asyncio_request import request


result = await request(
    url="https://api.fyndx1.de/masquerader/v1/aio-request-test/post",
    data={
        "first_name": "Joy",
        "last_name": "Pandey",
        "gender": "M"
    },
    protocol="HTTPS",
    protocol_info={
        "request_type": "POST"
    }
)

### Response
"""
{
  'url': 'https://api.fyndx1.de/masquerader/v1/aio-request-test/post',
  'payload': {
    'first_name': 'Joy',
    'last_name': 'Pandey',
    'gender': 'M'
  },
  'external_call_request_time': '2022-02-17 17:25:03.930531+05:30',
  'text': '',
  'error_message': '',
  'api_response': {
    'status_code': 200,
    'headers': {
      'Date': 'Thu, 17 Feb 2022 11:55:04 GMT',
      'Content-Type': 'application/json',
      'Content-Length': '57',
      'Connection': 'keep-alive',
      'X-Fynd-Trace-Id': '78ca02ff444ae5855e856c5f3d769364'
    },
    'cookies': {
      
    },
    'content': b'{"method": "POST", "status": true, "error_message": null}',
    'text': '{"method": "POST", "status": true, "error_message": null}',
    'json': {
      'method': 'POST',
      'status': True,
      'error_message': None
    },
    'request_tracer': [
      {
        'on_request_start': 287753.868594354,
        'is_redirect': False,
        'on_connection_create_start': 0.0002811980084516108,
        'on_dns_cache_miss': 0.002910615992732346,
        'on_dns_resolvehost_start': 0.0029266909696161747,
        'on_dns_resolvehost_end': 0.04894679499557242,
        'on_connection_create_end': 0.15098483895417303,
        'on_request_chunk_sent': 0.15202936198329553,
        'on_request_end': 0.2799108889885247
      }
    ]
  }
}
"""
```

* **API call with circuit breaker and custom exceptions**
```python
from asyncio_requests.asyncio_request import request


class HTTPRequestFailedException(Exception):
    pass


class CustomException(Exception):
    pass


def retry_exhausted_actions():
    print("All retries exhausted. API call failed.")
    
    
def request_attempt_failed_actions():
    print("API call failed.")
    
    
def request_abort_actions():
    print("API call aborted")


result = await request(
    url="https://api.fyndx1.de/masquerader/v1/aio-request-test/post",
    data={
        "first_name": "Joy",
        "last_name": "Pandey",
        "gender": "M"
    },
    protocol="HTTPS",
    protocol_info={
        "request_type": "POST",
        "circuit_breaker_config": {
            "maximum_failures": 5,
            "timeout": 15,
            "retry_config": {
                "name": "retry_masquerader",
                "allowed_retries": 5,
                "retriable_exceptions": [HTTPRequestFailedException],
                "abortable_exceptions": [CustomException],
                "on_retries_exhausted": retry_exhausted_actions,
                "on_failed_attempt": request_attempt_failed_actions,
                "on_abort": request_abort_actions,
                "delay": 5,
                "max_delay": 300,
                "jitter": True
            }
        }
    }
)

### Value of result
"""
{
  'url': 'https://api.fyndx1.de/masquerader/v1/aio-request-test/post',
  'payload': {
    'first_name': 'Joy',
    'last_name': 'Pandey',
    'gender': 'M'
  },
  'external_call_request_time': '2022-02-18 12:57:20.762713+05:30',
  'text': '',
  'error_message': '',
  'api_response': {
    'status_code': 200,
    'headers': {
      'Date': 'Fri, 18 Feb 2022 07:27:21 GMT',
      'Content-Type': 'application/json',
      'Content-Length': '57',
      'Connection': 'keep-alive',
      'X-Fynd-Trace-Id': '390cd5e9f4b1f179d5d711ca7bc83ec3'
    },
    'cookies': {
      
    },
    'content': b'{"method": "POST", "status": true, "error_message": null}',
    'text': '{"method": "POST", "status": true, "error_message": null}',
    'json': {
      'method': 'POST',
      'status': True,
      'error_message': None
    },
    'request_tracer': [
      {
        'on_request_start': 352622.180567606,
        'is_redirect': False,
        'on_connection_create_start': 0.0009668020065873861,
        'on_dns_cache_miss': 0.07304156001191586,
        'on_dns_resolvehost_start': 0.07307461701566353,
        'on_dns_resolvehost_end': 0.31564718199661,
        'on_connection_create_end': 0.5526716759777628,
        'on_request_chunk_sent': 0.5531467269756831,
        'on_request_end': 0.6851100819767453
      }
    ]
  }
}
"""
```

* **API with pre and post processor enabled with circuit breaker and retries.**
```python
from asyncio_requests.asyncio_request import request
from typing import Dict, Text


async def make_request_payload(response: Dict, first_name: Text, last_name: Text, gender: Text):
    response["payload"] = {
        "first_name": first_name,
        "last_name": last_name,
        "gender": gender
    }


async def print_response_recieved_from_api(response: Dict, text: Text):
    print(f"{text}{response['api_response']}")


result = await request(
    url="https://api.fyndx1.de/masquerader/v1/aio-request-test/post",
    protocol="HTTPS",
    protocol_info={
        "request_type": "POST",
        "circuit_breaker_config": {
            "timeout": 150,
            "retry_config": {
                "name": "api_retry",
                "allowed_retries": 4
            }
        }
    },
    pre_processor_config={
        "function": make_request_payload,
        "params": {
            "first_name": "Joy",
            "last_name": "Pandey",
            "gender": "M"
        }
    },
    post_processor_config={
        "function": print_response_recieved_from_api,
        "params": {
            "text": "Response received from API: "
        }
    }
)

### Response
### The pre and post processor keys have no values in response since they were just print statements. Had they been API calls, the value would have been different.
### The print statements will be printed in the shell if run but won't have its resemblence in the response.
"""
{
  'url': 'https://api.fyndx1.de/masquerader/v1/aio-request-test/post',
  'payload': {
    'first_name': 'Joy',
    'last_name': 'Pandey',
    'gender': 'M'
  },
  'external_call_request_time': '2022-02-17 17:33:35.508376+05:30',
  'text': '',
  'error_message': '',
  'pre_processor_response': None,
  'api_response': {
    'status_code': 200,
    'headers': {
      'Date': 'Thu, 17 Feb 2022 12:03:35 GMT',
      'Content-Type': 'application/json',
      'Content-Length': '57',
      'Connection': 'keep-alive',
      'X-Fynd-Trace-Id': '8903eeb30ed218385631d3b52d04b38e'
    },
    'cookies': {
      
    },
    'content': b'{"method": "POST", "status": true, "error_message": null}',
    'text': '{"method": "POST", "status": true, "error_message": null}',
    'json': {
      'method': 'POST',
      'status': True,
      'error_message': None
    },
    'request_tracer': [
      {
        'on_request_start': 288265.446420053,
        'is_redirect': False,
        'on_connection_create_start': 0.00028238497907295823,
        'on_dns_cache_miss': 0.0028724189614877105,
        'on_dns_resolvehost_start': 0.002888173970859498,
        'on_dns_resolvehost_end': 0.09302646096330136,
        'on_connection_create_end': 0.2075990799930878,
        'on_request_chunk_sent': 0.20890663599129766,
        'on_request_end': 0.319920428970363
      }
    ]
  },
  'post_processor_response': None
}
"""
```

* **Having separate API call in pre-processor.**
* This is usually the case wherein we want to report some data before making the actual API call
```python
from asyncio_requests.asyncio_request import request

result = await request(
    url="https://api.fyndx1.de/masquerader/v1/aio-request-test/post",
    data={
        "first_name": "Joy",
        "last_name": "Pandey",
        "gender": "M"
    },
    protocol="HTTPS",
    protocol_info={
        "request_type": "POST"
    },
    pre_processor_config={
        "function": request,
        "async_enabled": True,
        "params": {
            "url": "https://api.fyndx1.de/masquerader/v1/aio-request-test/post",
            "data": {
                "first_name": "Joy",
                "last_name": "Pandey",
                "Gender": "M"
            },
            "protocol": "HTTP",
            "protocol_info": {
                "request_type": "POST"
            }
        }
    }
)

### Value of result
"""
{
  'url': 'https://api.fyndx1.de/masquerader/v1/aio-request-test/post',
  'payload': {
    'first_name': 'Joy',
    'last_name': 'Pandey',
    'gender': 'M'
  },
  'external_call_request_time': '2022-02-18 13:26:35.575362+05:30',
  'text': '',
  'error_message': '',
  'pre_processor_response': {
    'url': 'https://api.fyndx1.de/masquerader/v1/aio-request-test/post',
    'payload': {
      'first_name': 'Joy',
      'last_name': 'Pandey',
      'Gender': 'M'
    },
    'external_call_request_time': '2022-02-18 13:26:35.575469+05:30',
    'text': '',
    'error_message': '',
    'api_response': {
      'status_code': 200,
      'headers': {
        'Date': 'Fri, 18 Feb 2022 07:56:36 GMT',
        'Content-Type': 'application/json',
        'Content-Length': '57',
        'Connection': 'keep-alive',
        'X-Fynd-Trace-Id': 'ae2703a4c82e8f917c53faded0688717'
      },
      'cookies': {},
      'content': b'{"method": "POST", "status": true, "error_message": null}',
      'text': '{"method": "POST", "status": true, "error_message": null}',
      'json': {
        'method': 'POST',
        'status': True,
        'error_message': None
      },
      'request_tracer': [
        {
          'on_request_start': 354376.993160197,
          'is_redirect': False,
          'on_connection_create_start': 0.00041024398524314165,
          'on_dns_cache_miss': 0.004407248983625323,
          'on_dns_resolvehost_start': 0.0044287089840509,
          'on_dns_resolvehost_end': 0.32443026901455596,
          'on_connection_create_end': 0.44923901598667726,
          'on_request_chunk_sent': 0.449799319030717,
          'on_request_end': 0.5300842020078562
        }
      ]
    }
  },
  'api_response': {
    'status_code': 200,
    'headers': {
      'Date': 'Fri, 18 Feb 2022 07:56:36 GMT',
      'Content-Type': 'application/json',
      'Content-Length': '57',
      'Connection': 'keep-alive',
      'X-Fynd-Trace-Id': 'ddb370fbf58999c359fe384b547446c9'
    },
    'cookies': {},
    'content': b'{"method": "POST", "status": true, "error_message": null}',
    'text': '{"method": "POST", "status": true, "error_message": null}',
    'json': {
      'method': 'POST',
      'status': True,
      'error_message': None
    },
    'request_tracer': [
      {
        'on_request_start': 354377.524928869,
        'is_redirect': False,
        'on_connection_create_start': 0.000421632023062557,
        'on_dns_cache_miss': 0.00067474803654477,
        'on_dns_resolvehost_start': 0.0006999420002102852,
        'on_dns_resolvehost_end': 0.002583371999207884,
        'on_connection_create_end': 0.09995210904162377,
        'on_request_chunk_sent': 0.10060718702152371,
        'on_request_end': 0.2261603070073761
      }
    ]
  }
}
"""
```

* **API call with nested pre and post processors**
* Here the pre processor(parent) has another pre-processor(child) within it.
* The response will include all the nested responses in the same fashion as that of the config set
* The actual flow would be (child pre-processor -> parent pre-processor -> main API call -> parent post-processor -> child post processor)
* response format will be this way -
```
    parent pre-processor response
        child pre processor response
            child's child pre preprocesor response
                infinite nesting...
    
    main api call response
    
    parent post-processor response
        child post processor response
            child's child post preprocesor response
                infinite nesting...
```

```python
from asyncio_requests.asyncio_request import request


async def test_fun(*args, **kwargs):
    return {"text": "final res"}


result = await request(
    url="https://api.fyndx1.de/masquerader/v1/aio-request-test/post",
    data={
        "first_name": "Joy",
        "last_name": "Pandey",
        "Gender": "M"
    },
    protocol="HTTP",
    protocol_info={
        "request_type": "POST",
        "circuit_breaker_config": {
            "timeout": 150,
            "retry_config": {
                "name": "asdf",
                "allowed_retries": 1
            }
        }
    },
    pre_processor_config={
        "function": request,
        "async_enabled": True,
        "params": {
            "url": "https://api.fyndx1.de/masquerader/v1/aio-request-test/post",
            "data": {
                "first_name": "Joy",
                "last_name": "Pandey",
                "Gender": "M"
            },
            "protocol": "HTTP",
            "protocol_info": {
                "request_type": "POST",
                "circuit_breaker_config": {
                    "retry_config": {
                        "name": "asdf",
                        "allowed_retries": 5
                    }
                },
            },
            "pre_processor_config": {
                "function": test_fun,
                "async_enabled": True,
                "params": {
                    "url": "https://api.fyndx1.de/masquerader/v1/aio-request-test/post",
                    "data": {
                        "first_name": "Joy",
                        "last_name": "Pandey",
                        "Gender": "M"
                    },
                    "protocol": "HTTP",
                    "protocol_info": {
                        "request_type": "POST",
                        "circuit_breaker_config": {
                            "retry_config": {
                                "name": "asdf",
                                "allowed_retries": 5
                            }
                        },
                    }
                }
            }
        }
    },
    post_processor_config={
        "function": request,
        "async_enabled": True,
        "params": {
            "url": "https://api.fyndx1.de/masquerader/v1/aio-request-test/post",
            "data": {
                "first_name": "Joy",
                "last_name": "Pandey",
                "Gender": "M"
            },
            "protocol": "HTTP",
            "protocol_info": {
                "request_type": "POST",
                "circuit_breaker_config": {
                    "retry_config": {
                        "name": "asdf",
                        "allowed_retries": 5
                    }
                },
            },
            "post_processor_config": {
                "function": test_fun,
                "async_enabled": True,
                "params": {
                    "url": "https://api.fyndx1.de/masquerader/v1/aio-request-test/post",
                    "data": {
                        "first_name": "Joy",
                        "last_name": "Pandey",
                        "Gender": "M"
                    },
                    "protocol": "HTTP",
                    "protocol_info": {
                        "request_type": "POST",
                        "circuit_breaker_config": {
                            "retry_config": {
                                "name": "asdf",
                                "allowed_retries": 5
                            }
                        },
                    }
                }
            }
        }
    }
)

### Response
"""
{
  'url': 'https://api.fyndx1.de/masquerader/v1/aio-request-test/post',
  'payload': {
    'first_name': 'Joy',
    'last_name': 'Pandey',
    'Gender': 'M'
  },
  'external_call_request_time': '2022-02-17 17:22:01.383304+05:30',
  'text': '',
  'error_message': '',
  'pre_processor_response': {
    'url': 'https://api.fyndx1.de/masquerader/v1/aio-request-test/post',
    'payload': {
      'first_name': 'Joy',
      'last_name': 'Pandey',
      'Gender': 'M'
    },
    'external_call_request_time': '2022-02-17 17:22:01.383358+05:30',
    'text': '',
    'error_message': '',
    'pre_processor_response': {
      'text': 'final res'
    },
    'api_response': {
      'status_code': 200,
      'headers': {
        'Date': 'Thu, 17 Feb 2022 11:52:01 GMT',
        'Content-Type': 'application/json',
        'Content-Length': '57',
        'Connection': 'keep-alive',
        'X-Fynd-Trace-Id': 'b1a3111270067ae160eeaf9971b04cc5'
      },
      'cookies': {
        
      },
      'content': b'{"method": "POST", "status": true, "error_message": null}',
      'text': '{"method": "POST", "status": true, "error_message": null}',
      'json': {
        'method': 'POST',
        'status': True,
        'error_message': None
      },
      'request_tracer': [
        {
          'on_request_start': 287571.321608293,
          'is_redirect': False,
          'on_connection_create_start': 0.00031328899785876274,
          'on_dns_cache_miss': 0.0029667950002476573,
          'on_dns_resolvehost_start': 0.0029829980339854956,
          'on_dns_resolvehost_end': 0.0064852479845285416,
          'on_connection_create_end': 0.08529951400123537,
          'on_request_chunk_sent': 0.0858444279874675,
          'on_request_end': 0.1671372150303796
        }
      ]
    }
  },
  'api_response': {
    'status_code': 200,
    'headers': {
      'Date': 'Thu, 17 Feb 2022 11:52:01 GMT',
      'Content-Type': 'application/json',
      'Content-Length': '57',
      'Connection': 'keep-alive',
      'X-Fynd-Trace-Id': '3340481533a6511b15952cabb4c144bb'
    },
    'cookies': {
      
    },
    'content': b'{"method": "POST", "status": true, "error_message": null}',
    'text': '{"method": "POST", "status": true, "error_message": null}',
    'json': {
      'method': 'POST',
      'status': True,
      'error_message': None
    },
    'request_tracer': [
      {
        'on_request_start': 287571.490459029,
        'is_redirect': False,
        'on_connection_create_start': 0.0006432340014725924,
        'on_dns_cache_miss': 0.00104641099460423,
        'on_dns_resolvehost_start': 0.001091104990337044,
        'on_dns_resolvehost_end': 0.0037200640072114766,
        'on_connection_create_end': 0.10335264401510358,
        'on_request_chunk_sent': 0.10410607699304819,
        'on_request_end': 0.18950222100829706
      }
    ]
  },
  'post_processor_response': {
    'url': 'https://api.fyndx1.de/masquerader/v1/aio-request-test/post',
    'payload': {
      'first_name': 'Joy',
      'last_name': 'Pandey',
      'Gender': 'M'
    },
    'external_call_request_time': '2022-02-17 17:22:01.743288+05:30',
    'text': '',
    'error_message': '',
    'api_response': {
      'status_code': 200,
      'headers': {
        'Date': 'Thu, 17 Feb 2022 11:52:02 GMT',
        'Content-Type': 'application/json',
        'Content-Length': '57',
        'Connection': 'keep-alive',
        'X-Fynd-Trace-Id': 'a0304896aabbc394894d442fa27a5c3e'
      },
      'cookies': {
        
      },
      'content': b'{"method": "POST", "status": true, "error_message": null}',
      'text': '{"method": "POST", "status": true, "error_message": null}',
      'json': {
        'method': 'POST',
        'status': True,
        'error_message': None
      },
      'request_tracer': [
        {
          'on_request_start': 287571.681455504,
          'is_redirect': False,
          'on_connection_create_start': 0.00041248503839597106,
          'on_dns_cache_miss': 0.0006613450241275132,
          'on_dns_resolvehost_start': 0.0006853759987279773,
          'on_dns_resolvehost_end': 0.0024919320130720735,
          'on_connection_create_end': 0.08381915499921888,
          'on_request_chunk_sent': 0.08448734600096941,
          'on_request_end': 0.5899507160065696
        }
      ]
    },
    'post_processor_response': {
      'text': 'final res'
    }
  }
}
"""
```

* **API call to send a file**
* Here we are downloading a file in the pre-processor. If the file is already present in the system then you can avoid that pre-processor and directly mention the file address in the local_file_path variable.
* The file can be downloaded by using the existing pre processor function in the utilities.
* The Utilities dir has a function that supports file download via url/aws s3.
* The Utilities dir also has a function to delete a file. If you want to delete teh file post making the API call, use this in the post processor.
* If you have some other way around to download the file, just pass that function address in the pre processor and include the file address in the local_file_path variable.

```python
from asyncio_requests.asyncio_request import request
from asyncio_requests.utils.http_file_upload_config import download_file_from_url, delete_local_file_path


local_file_path = "/tmp/test.pdf"
result = await request(
    url="http://localhost:5000/api/v1/test/aio-request-files",
    protocol="HTTPS",
    protocol_info={
        "request_type": "POST",
        "http_file_upload_config": {
            "local_filepath": local_file_path,
            "file_key": "file"
        }
    },
    pre_processor_config={
        "function": download_file_from_url,
        "params": {
            "file_download_path": "https://didukhn.github.io/homepage/assets/img/photo.jpg",
            "local_filepath": local_file_path
        }
    },
    post_processor_config={
        "function": delete_local_file_path,
        "params": {
            "local_filepath": local_file_path
        }
    }
)

### Response
"""
{
  'url': 'http://localhost:5000/api/v1/test/aio-request-files',
  'payload': {
    
  },
  'external_call_request_time': '2022-02-17 17:13:03.231826+05:30',
  'text': '',
  'error_message': '',
  'pre_processor_response': None,
  'api_response': {
    'status_code': 200,
    'headers': {
      'Connection': 'close',
      'Content-Length': '29',
      'Content-Type': 'application/json'
    },
    'cookies': {
      
    },
    'content': b'{"success":true,"message":""}',
    'text': '{"success":true,"message":""}',
    'json': {
      'success': True,
      'message': ''
    },
    'request_tracer': [
      {
        'on_request_start': 287033.4935145,
        'is_redirect': False,
        'on_connection_create_start': 0.0021707930136471987,
        'on_dns_cache_miss': 0.002449413004796952,
        'on_dns_resolvehost_start': 0.002480961033143103,
        'on_dns_resolvehost_end': 0.003233974042814225,
        'on_connection_create_end': 0.0042467640014365315,
        'on_request_chunk_sent': 0.0064254660392180085,
        'on_request_end': 0.1773580180015415
      }
    ]
  },
  'post_processor_response': None
}
"""
```

**Utilities Included**
* Download a file from AWS S3
* Download a file from public url
* Delete a local file on system


## FTP

* Uses aioftp internally to implement FTP/FTPS.
* Added functionality of circuit breaker, pre and post processor configs same as http.
* Can Leverage all the ftp commands provided by aioftp library.
* By Default used FTP protocol can use FTPS if ssl config is enabled.


# How to use.

```python
import aiohttp
from asyncio_requests.asyncio_request import request

await request(
    url="Server Ip",  # Ip/url or ftp server <Required>
    auth=aiohttp.BasicAuth('username', 'password'),  # The username and ip of the ftp server, to be sent as aiohttp.BasicAuth object <Required>
    protocol="FTP",  # str <Required> (FTP)
    protocol_info={
        "port": 21, # default is 21 <Optional>
        "command": "download", # generic ftp commands like download, upload, remove <Required>
        "server_path": "/tmp/temp.pdf", # path from where to get/remove or upload file on server.
        "client_path": "", # path where file is downloaded/uploaded to. <optional>
        "timeout": 30 # <Optional>
        "verify_ssl": False # default is False <Optional>
        "certificate" "" # required if verify_ssl is True
        "circuit_breaker_config": {  # Optional
            "maximum_failures": int,  # Optional Failures allowed
            "timeout": int,  # Optional time in seconds
            "retry_config": {  # Optional Include this if you want retry calls if failed on first time
                "name": str,  # Required Any name
                "allowed_retries": int,  # Required number of retries you want to make
                "retriable_exceptions": [<callable object>] # Optional
                "abortable_exceptions": [<callable object>] # Optional
                "on_retries_exhausted": <callable object>, # Optional callable that will be invoked on a retries exhausted event,
                "on_failed_attempt": <callable object>, # Optional callable that will be invoked on a failed attempt event,
                "on_abort": <callable object>, # Optional callable that will be invoked on an abort event,
            "delay": int, # seconds of delay between retries Optional default 0,
            "max_delay": int, # seconds of max delay between retries Optional default 0,
            "jitter": bool # Boolean Optional,
          }
      }
    },
    pre_processor_config = {  # Optional
        "function": <callable object>,  # Required function that you want to call before ftp call
        "params": {  # Optional
            "param1": "value1" # Params you want to pass in function
        }
    },
    post_processor_config = {  # Optional
        "function": <callable object>,  # Required function that you want to call after ftp call
        "params": {
            "param1": "value1" # Params you want to pass in function
        }
    }
)
```

# Sample FTP Call.

```python
import aiohttp
from asyncio_requests.asyncio_request import request
from asyncio_requests.utils.http_file_upload_config import download_file_from_url

local_path = "/tmp/temp.png"
await request(
    url = "localhost",
    auth = aiohttp.BasicAuth("use","pswd"),
    protocol = "FTP",
    protocol_info = {
        "port": 21,
        "command": "upload",
        "server_path": "/home/resources/logo.png",
        "client_path": local_path,
        "circuit_breaker_config": {
            "timeout": 150,
            "retry_config": {
                "name": "api_retry",
                "allowed_retries": 4
            }
        }
    },
    pre_processor_config = {
        "function": download_file_from_s3,
          "params": {
            "file_download_path": "https://[bucket_name].s3.amazonaws.com/logo.png",
            "local_filepath": local_path
          }
    }
)

## Resonse
"""
{'api_response': True,
 'error_message': '',
 'external_call_request_time': '2022-05-13 19:07:35.775706+05:30',
 'file': '/home/resources/logo.png',
 'file_stats': {'modify': '20220513190700',
                'size': '29304',
                'type': 'file',
                'unix.group': '1000',
                'unix.links': '1',
                'unix.mode': 436,
                'unix.owner': '1000'},
 'mode': 'upload',
 'payload': {'success': True},
 'pre_processor_response': None,
 'text': '',
 'url': 'localhost'}

"""

```


## SOAP
(upcoming)


### Generating Distribution Archives

```bash
python3 -m pip install --upgrade setuptools wheel
python3 setup.py sdist bdist_wheel
```
This command should output a lot of text and once completed should generate two files in the `dist` directory.

## Open Source contribution 

You can add utilities that can be used by others. 

Eg - Contributing a function that accepts certain params and downloads a file via AWS S3.
This function can be used by other developers in the pre/post processor to download the file before or after making the API call.

**Make sure to add the utility in the utilities section in the readme wrt protocol.**

### Generating New Tags/Release

 - Check the code with flake8, mypy, bandit, pytest before submitting a PR
 - Update version in [setup.py](setup.py)
 - Update version in [docs/source/conf.py](docs/source/conf.py)
 - Update version in README.md section
 - Send a PR, and after it gets merged to master create a tag from master in the format `vX.Y`
   - `X` - Major Release (Breaking Changes)
   - `Y` - Minor Release
 

**To know more about the developer, here's a quote to find him out -** 
```Anton died so we could live```