From cb8847b5d180b5bc59c69eebf6ac6847698bf6eb Mon Sep 17 00:00:00 2001 From: Tim Jarrett Date: Wed, 29 Mar 2023 10:28:10 -0400 Subject: [PATCH] Fix #63 handle retries better, add sample script --- pyproject.toml | 4 ++-- samples/getself.py | 6 +++++ veracode_api_py/apihelper.py | 44 +++++++++++++++++++++++------------- 3 files changed, 36 insertions(+), 18 deletions(-) create mode 100644 samples/getself.py diff --git a/pyproject.toml b/pyproject.toml index 27d8af5..48623aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = 'veracode_api_py' -version = '0.9.38' +version = '0.9.39' authors = [ {name = "Tim Jarrett", email="tjarrett@veracode.com"} ] description = 'Python helper library for working with the Veracode APIs. Handles retries, pagination, and other features of the modern Veracode REST APIs.' readme = 'README.md' @@ -22,4 +22,4 @@ dependencies = {file = ["requirements.txt"]} [project.urls] "Homepage" = "https://github.com/veracode/veracode-api-py" "Bug Tracker" = "https://github.com/veracode/veracode-api-py/issues" -"Download" = "https://github.com/veracode/veracode-api-py/archive/v_0938.tar.gz" \ No newline at end of file +"Download" = "https://github.com/veracode/veracode-api-py/archive/v_0939.tar.gz" \ No newline at end of file diff --git a/samples/getself.py b/samples/getself.py new file mode 100644 index 0000000..5dbcc7c --- /dev/null +++ b/samples/getself.py @@ -0,0 +1,6 @@ +# a simple sample to get attributes from the user's login information +from veracode_api_py import Users + +me = Users().get_self() + +print("You are {}".format(me['user_name'])) \ No newline at end of file diff --git a/veracode_api_py/apihelper.py b/veracode_api_py/apihelper.py index 8641c01..0320ec6 100644 --- a/veracode_api_py/apihelper.py +++ b/veracode_api_py/apihelper.py @@ -6,7 +6,6 @@ import json import time from requests.adapters import HTTPAdapter -from requests.packages.urllib3.util.retry import Retry from veracode_api_signing.exceptions import VeracodeAPISigningException from veracode_api_signing.plugin_requests import RequestsAuthPluginVeracodeHMAC @@ -27,7 +26,6 @@ class APIHelper(): def __init__(self, debug=False): self.baseurl = self._get_baseurl() - requests.Session().mount(self.baseurl, HTTPAdapter(max_retries=3)) self.base_rest_url = self._get_baseresturl() self.retry_seconds = 120 self.connect_error_msg = "Connection Error" @@ -52,18 +50,30 @@ def _get_region_url(self, type): elif type == 'rest': return Constants().REGIONS[self.region]['base_rest_url'] + def _check_for_errors(self,theresponse, *args, **kwargs): + if theresponse.status_code in (429, 502, 503, 504): + # retry by populating new prepared request from the request in the response object + # and recalculating auth + logger.debug("Retrying request, error code {} received".format(theresponse.status_code)) + session = requests.Session() + oldreq = theresponse.request + oldheaders = oldreq.headers + del oldheaders['authorization'] + newreq = requests.Request(oldreq.method,oldreq.url,auth=RequestsAuthPluginVeracodeHMAC(), + headers=oldheaders) + return session.send(newreq.prepare()) + + def _prepare_headers(self,method,apifamily): + headers = {"User-Agent": "api.py"} + if method in ["POST", "PUT"] and apifamily=='json': + headers.update({'Content-type': 'application/json'}) + return headers + def _rest_request(self, url, method, params=None, body=None, fullresponse=False, use_base_url=True): # base request method for a REST request - myheaders = {"User-Agent": "api.py"} - if method in ["POST", "PUT"]: - myheaders.update({'Content-type': 'application/json'}) - - retry_strategy = Retry(total=3, - status_forcelist=[429, 500, 502, 503, 504], - method_whitelist=["HEAD", "GET", "OPTIONS"] - ) + myheaders = self._prepare_headers(method,'json') + session = requests.Session() - session.mount(self.base_rest_url, HTTPAdapter(max_retries=retry_strategy)) if use_base_url: url = self.base_rest_url + url @@ -71,7 +81,8 @@ def _rest_request(self, url, method, params=None, body=None, fullresponse=False, try: if method == "GET": request = requests.Request(method, url, params=params, auth=RequestsAuthPluginVeracodeHMAC(), - headers=myheaders) + headers=myheaders, + hooks={'response': self._check_for_errors}) prepared_request = request.prepare() r = session.send(prepared_request) elif method == "POST": @@ -85,14 +96,15 @@ def _rest_request(self, url, method, params=None, body=None, fullresponse=False, else: raise VeracodeAPIError("Unsupported HTTP method") except requests.exceptions.RequestException as e: - logger.exception(self.connect_error_msg) + logger.exception("Error: {}".format(self.connect_error_msg)) raise VeracodeAPIError(e) - if not (r.status_code == requests.codes.ok): + if r.status_code != requests.codes.ok: logger.debug("API call returned non-200 HTTP status code: {}".format(r.status_code)) if not (r.ok): - logger.debug("Error retrieving data. HTTP status code: {}".format(r.status_code)) + conv_id = r.headers['x-conversation-id'] + logger.debug("Error retrieving data. HTTP status code: {}, conversation id {}".format(r.status_code,conv_id)) if r.status_code == 401: logger.exception( "Error [{}]: {} for request {}. Check that your Veracode API account credentials are correct.".format( @@ -135,7 +147,7 @@ def _xml_request(self, url, method, params=None, files=None): session = requests.Session() session.mount(self.baseurl, HTTPAdapter(max_retries=3)) request = requests.Request(method, url, params=params, files=files, - auth=RequestsAuthPluginVeracodeHMAC(), headers={"User-Agent": "api.py"}) + auth=RequestsAuthPluginVeracodeHMAC(), headers=self._prepare_headers(method,'xml')) prepared_request = request.prepare() r = session.send(prepared_request) if 200 <= r.status_code <= 299: