From 9675f7879f7dfe81aed19c7c219f652469d49cd2 Mon Sep 17 00:00:00 2001 From: xjxckk <17519346+xjxckk@users.noreply.github.com> Date: Thu, 17 Feb 2022 12:12:26 +0000 Subject: [PATCH 1/9] Add files via upload --- ebaysdk/connection.py | 45 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index a29a644..6cff827 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -13,7 +13,7 @@ import uuid import webbrowser -from requests import Request, Session +from requests import Request, Session, post from requests.adapters import HTTPAdapter from xml.dom.minidom import parseString @@ -25,6 +25,17 @@ from ebaysdk.response import Response from ebaysdk.exception import ConnectionError, ConnectionResponseError +## Added this / https://github.com/timotheus/ebaysdk-python/issues/347 +from ebaysdk.config import Config +from base64 import b64encode + +from requests.structures import CaseInsensitiveDict +import json +import datetime +time_of_last_token = datetime.datetime.min +previous_access_token = "" +## End of changes + HTTP_SSL = { False: 'http', True: 'https', @@ -40,11 +51,23 @@ def __init__(self, debug=False, method='GET', if debug: set_stream_logger() + + ## Added this + self.config = Config(domain=kwargs.get('domain', 'open.api.ebay.com'), connection_kwargs=kwargs, config_file=kwargs.get('config_file', 'ebay.yaml')) + + # Building OAuth token + client_id = self.config.get('appid', '') + client_secret = self.config.get('certid', '') + token_string = f'{client_id}:{client_secret}' + token_bytes = token_string.encode('ascii') + token_base64 = b64encode(token_bytes) + self.final_token = token_base64.decode('ascii') + ## End of changes self.response = None self.request = None self.verb = None - self.config = None + # self.config = None self.debug = debug self.method = method self.timeout = timeout @@ -158,6 +181,24 @@ def build_request(self, verb, data, verb_attrs, files=None): headers=headers, files=files, ) + + ## Added this / https://github.com/timotheus/ebaysdk-python/issues/347 + + time_now = datetime.datetime.now() + time_difference_last_token = time_now - time_of_last_token + if time_difference_last_token.total_seconds() > 3600: + url = "https://api.ebay.com/identity/v1/oauth2/token" + headers = CaseInsensitiveDict() + headers["Content-Type"] = "application/x-www-form-urlencoded" + headers["Authorization"] = f"Basic {self.final_token}" + data = "grant_type=client_credentials&scope=https%3A%2F%2Fapi.ebay.com%2Foauth%2Fapi_scope" + response = post(url, headers=headers, data=data) + response_dict = json.loads(response.text) + access_token = response_dict['access_token'] + previous_access_token = access_token + request.headers['X-EBAY-API-IAF-TOKEN'] = access_token + + ## End of changes self.request = request.prepare() From 640cf5238a5fb78061847bd937bb05c5164e37c9 Mon Sep 17 00:00:00 2001 From: xjxckk <17519346+xjxckk@users.noreply.github.com> Date: Sat, 19 Feb 2022 15:04:58 +0000 Subject: [PATCH 2/9] Update connection.py --- ebaysdk/connection.py | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index 6cff827..73d064f 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -24,17 +24,12 @@ from ebaysdk.utils import getValue, smart_encode_request_data from ebaysdk.response import Response from ebaysdk.exception import ConnectionError, ConnectionResponseError - -## Added this / https://github.com/timotheus/ebaysdk-python/issues/347 from ebaysdk.config import Config from base64 import b64encode from requests.structures import CaseInsensitiveDict import json import datetime -time_of_last_token = datetime.datetime.min -previous_access_token = "" -## End of changes HTTP_SSL = { False: 'http', @@ -52,8 +47,8 @@ def __init__(self, debug=False, method='GET', if debug: set_stream_logger() - ## Added this self.config = Config(domain=kwargs.get('domain', 'open.api.ebay.com'), connection_kwargs=kwargs, config_file=kwargs.get('config_file', 'ebay.yaml')) + self.time_of_last_token = datetime.datetime.min # Building OAuth token client_id = self.config.get('appid', '') @@ -62,7 +57,6 @@ def __init__(self, debug=False, method='GET', token_bytes = token_string.encode('ascii') token_base64 = b64encode(token_bytes) self.final_token = token_base64.decode('ascii') - ## End of changes self.response = None self.request = None @@ -181,24 +175,23 @@ def build_request(self, verb, data, verb_attrs, files=None): headers=headers, files=files, ) - - ## Added this / https://github.com/timotheus/ebaysdk-python/issues/347 time_now = datetime.datetime.now() - time_difference_last_token = time_now - time_of_last_token + time_difference_last_token = time_now - self.time_of_last_token + print('Time difference last token', time_difference_last_token) if time_difference_last_token.total_seconds() > 3600: - url = "https://api.ebay.com/identity/v1/oauth2/token" + url = 'https://api.ebay.com/identity/v1/oauth2/token' headers = CaseInsensitiveDict() - headers["Content-Type"] = "application/x-www-form-urlencoded" - headers["Authorization"] = f"Basic {self.final_token}" - data = "grant_type=client_credentials&scope=https%3A%2F%2Fapi.ebay.com%2Foauth%2Fapi_scope" + headers['Content-Type'] = 'application/x-www-form-urlencoded' + headers['Authorization'] = f'Basic {self.final_token}' + data = 'grant_type=client_credentials&scope=https%3A%2F%2Fapi.ebay.com%2Foauth%2Fapi_scope' response = post(url, headers=headers, data=data) - response_dict = json.loads(response.text) + response_dict = response.json() + print('Oauth2.0 token request response:', json.dumps(response_dict, indent=4)) access_token = response_dict['access_token'] - previous_access_token = access_token request.headers['X-EBAY-API-IAF-TOKEN'] = access_token - - ## End of changes + + self.time_of_last_token = time_now self.request = request.prepare() From ad881d89786510ca9cb9197b255967945f4665a7 Mon Sep 17 00:00:00 2001 From: xjxckk <17519346+xjxckk@users.noreply.github.com> Date: Sat, 19 Feb 2022 15:08:39 +0000 Subject: [PATCH 3/9] Update connection.py --- ebaysdk/connection.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index 73d064f..04cde1e 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -178,7 +178,6 @@ def build_request(self, verb, data, verb_attrs, files=None): time_now = datetime.datetime.now() time_difference_last_token = time_now - self.time_of_last_token - print('Time difference last token', time_difference_last_token) if time_difference_last_token.total_seconds() > 3600: url = 'https://api.ebay.com/identity/v1/oauth2/token' headers = CaseInsensitiveDict() @@ -187,7 +186,6 @@ def build_request(self, verb, data, verb_attrs, files=None): data = 'grant_type=client_credentials&scope=https%3A%2F%2Fapi.ebay.com%2Foauth%2Fapi_scope' response = post(url, headers=headers, data=data) response_dict = response.json() - print('Oauth2.0 token request response:', json.dumps(response_dict, indent=4)) access_token = response_dict['access_token'] request.headers['X-EBAY-API-IAF-TOKEN'] = access_token From 3499a58b09d95d38b1c2b4d6ebb9faa7b6ba01ac Mon Sep 17 00:00:00 2001 From: xjxckk <17519346+xjxckk@users.noreply.github.com> Date: Mon, 28 Mar 2022 17:24:53 +0100 Subject: [PATCH 4/9] Add files via upload --- connection.py | 348 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 connection.py diff --git a/connection.py b/connection.py new file mode 100644 index 0000000..cb512f5 --- /dev/null +++ b/connection.py @@ -0,0 +1,348 @@ +# -*- coding: utf-8 -*- + +''' +Copyright 2012-2019 eBay Inc. +Authored by: Tim Keefer +Licensed under CDDL 1.0 +''' + +from ebaysdk import log + +import re +import time +import uuid +import webbrowser + +from requests import Request, Session, post +from requests.adapters import HTTPAdapter + +from xml.dom.minidom import parseString +from xml.parsers.expat import ExpatError + +from ebaysdk import set_stream_logger, UserAgent +from ebaysdk.utils import getNodeText as getNodeTextUtils, smart_encode, smart_decode +from ebaysdk.utils import getValue, smart_encode_request_data +from ebaysdk.response import Response +from ebaysdk.exception import ConnectionError, ConnectionResponseError + +HTTP_SSL = { + False: 'http', + True: 'https', +} + + +class BaseConnection(object): + """Base Connection Class.""" + + def __init__(self, debug=False, method='GET', + proxy_host=None, timeout=20, proxy_port=80, + parallel=None, escape_xml=False, **kwargs): + + if debug: + set_stream_logger() + + self.response = None + self.request = None + self.verb = None + self.config = None + self.debug = debug + self.method = method + self.timeout = timeout + self.proxy_host = proxy_host + self.proxy_port = proxy_port + self.escape_xml = escape_xml + self.datetime_nodes = [] + self._list_nodes = [] + + self.proxies = dict() + if self.proxy_host: + proxy = 'http://%s:%s' % (self.proxy_host, self.proxy_port) + self.proxies = { + 'http': proxy, + 'https': proxy + } + + self.session = Session() + self.session.mount('http://', HTTPAdapter(max_retries=3)) + self.session.mount('https://', HTTPAdapter(max_retries=3)) + + self.parallel = parallel + + self.base_list_nodes = [] + self.datetime_nodes = [] + + self._reset() + + def debug_callback(self, debug_type, debug_message): + log.debug('type: ' + str(debug_type) + ' message' + str(debug_message)) + + def v(self, *args, **kwargs): + return getValue(self.response.dict(), *args, **kwargs) + + def getNodeText(self, nodelist): + return getNodeTextUtils(nodelist) + + def _reset(self): + self.response = None + self.request = None + self.verb = None + self._list_nodes = [] + self._request_id = None + self._request_dict = {} + self._time = time.time() + self._response_content = None + self._response_dom = None + self._response_obj = None + self._response_soup = None + self._response_dict = None + self._response_error = None + self._resp_body_errors = [] + self._resp_body_warnings = [] + self._resp_codes = [] + + def _add_prefix(self, nodes, verb): + if verb: + for i, v in enumerate(nodes): + if not nodes[i].startswith(verb.lower()): + nodes[i] = "%sresponse.%s" % ( + verb.lower(), nodes[i].lower()) + + def execute(self, verb, data=None, list_nodes=[], verb_attrs=None, files=None): + "Executes the HTTP request." + log.debug('execute: verb=%s data=%s' % (verb, data)) + + self._reset() + + self._list_nodes += list_nodes + self._add_prefix(self._list_nodes, verb) + + if hasattr(self, 'base_list_nodes'): + self._list_nodes += self.base_list_nodes + + self.build_request(verb, data, verb_attrs, files) + self.execute_request() + + if hasattr(self.response, 'content'): + self.process_response() + self.error_check() + + log.debug('total time=%s' % (time.time() - self._time)) + + return self.response + + def build_request(self, verb, data, verb_attrs, files=None): + + self.verb = verb + self._request_dict = data + self._request_id = uuid.uuid4() + + url = self.build_request_url(verb) + + headers = self.build_request_headers(verb) + headers.update({'User-Agent': UserAgent, + 'X-EBAY-SDK-REQUEST-ID': str(self._request_id)}) + + # if we are adding files, we ensure there is no Content-Type header already defined + # otherwise Request will use the existing one which is likely not to be multipart/form-data + # data must also be a dict so we make it so if needed + + requestData = self.build_request_data(verb, data, verb_attrs) + if files: + del(headers['Content-Type']) + if isinstance(requestData, str): # pylint: disable-msg=E0602 + requestData = {'XMLPayload': requestData} + + request = Request(self.method, + url, + data=smart_encode_request_data(requestData), + headers=headers, + files=files, + ) + + self.request = request.prepare() + + def build_request_headers(self, verb): + return {} + + def build_request_data(self, verb, data, verb_attrs): + return "" + + def build_request_url(self, verb): + url = "%s://%s%s" % ( + HTTP_SSL[self.config.get('https', True)], + self.config.get('domain'), + self.config.get('uri') + ) + return url + + def execute_request(self): + + log.debug("REQUEST (%s): %s %s" + % (self._request_id, self.request.method, self.request.url)) + log.debug('headers=%s' % self.request.headers) + log.debug('body=%s' % self.request.body) + + if self.parallel: + self.parallel._add_request(self) + return None + + self.response = self.session.send(self.request, + verify=True, + proxies=self.proxies, + timeout=self.timeout, + allow_redirects=True + ) + + log.debug('RESPONSE (%s):' % self._request_id) + log.debug('elapsed time=%s' % self.response.elapsed) + log.debug('status code=%s' % self.response.status_code) + log.debug('headers=%s' % self.response.headers) + log.debug('content=%s' % self.response.text) + + def process_response(self, parse_response=True): + """Post processing of the response""" + + self.response = Response(self.response, + verb=self.verb, + list_nodes=self._list_nodes, + datetime_nodes=self.datetime_nodes, + parse_response=parse_response) + + self.session.close() + # set for backward compatibility + self._response_content = self.response.content + + if self.response.status_code != 200: + self._response_error = self.response.reason + + def error_check(self): + estr = self.error() + + if estr and self.config.get('errors', True): + log.error(estr) + raise ConnectionError(estr, self.response) + + def response_codes(self): + return self._resp_codes + + def response_status(self): + "Retuns the HTTP response status string." + + return self.response.reason + + def response_code(self): + "Returns the HTTP response status code." + + return self.response.status_code + + def response_content(self): + return self.response.content + + def response_soup(self): + "Returns a BeautifulSoup object of the response." + + if not self._response_soup: + try: + from bs4 import BeautifulStoneSoup + except ImportError: + from BeautifulSoup import BeautifulStoneSoup + log.warning( + 'DeprecationWarning: BeautifulSoup 3 or earlier is deprecated; install bs4 instead\n') + + self._response_soup = BeautifulStoneSoup( + smart_decode(self.response_content) + ) + + return self._response_soup + + def response_obj(self): + log.warning('response_obj() DEPRECATED, use response.reply instead') + return self.response.reply + + def response_dom(self): + """ Deprecated: use self.response.dom() instead + Returns the response DOM (xml.dom.minidom). + """ + log.warning('response_dom() DEPRECATED, use response.dom instead') + + if not self._response_dom: + dom = None + content = None + + try: + if self.response.content: + regex = re.compile(b'xmlns="[^"]+"') + content = regex.sub(b'', self.response.content) + else: + content = "<%sResponse>" % ( + self.verb, self.verb) + + dom = parseString(content) + self._response_dom = dom.getElementsByTagName( + self.verb + 'Response')[0] + + except ExpatError as e: + raise ConnectionResponseError( + "Invalid Verb: %s (%s)" % (self.verb, e), self.response) + except IndexError: + self._response_dom = dom + + return self._response_dom + + def response_dict(self): + "Returns the response dictionary." + log.warning( + 'response_dict() DEPRECATED, use response.dict() or response.reply instead') + + return self.response.reply + + def response_json(self): + "Returns the response JSON." + log.warning('response_json() DEPRECATED, use response.json() instead') + + return self.response.json() + + def _get_resp_body_errors(self): + """Parses the response content to pull errors. + + Child classes should override this method based on what the errors in the + XML response body look like. They can choose to look at the 'ack', + 'Errors', 'errorMessage' or whatever other fields the service returns. + the implementation below is the original code that was part of error() + """ + + if self._resp_body_errors and len(self._resp_body_errors) > 0: + return self._resp_body_errors + + errors = [] + + if self.verb is None: + return errors + + dom = self.response.dom() + if dom is None: + return errors + + return [] + + def error(self): + "Builds and returns the api error message." + + error_array = [] + if self._response_error: + error_array.append(self._response_error) + + error_array.extend(self._get_resp_body_errors()) + + if len(error_array) > 0: + # Force all errors to be unicode in a proper way + error_array = [smart_decode(smart_encode(e)) for e in error_array] + error_string = u"{verb}: {message}".format( + verb=self.verb, message=u", ".join(error_array)) + + return error_string + + return None + + def opendoc(self): + webbrowser.open(self.config.get('doc_url')) From f67802ff6d839a4468618b809d166cf3cdb120b4 Mon Sep 17 00:00:00 2001 From: xjxckk <17519346+xjxckk@users.noreply.github.com> Date: Mon, 28 Mar 2022 17:25:09 +0100 Subject: [PATCH 5/9] Delete connection.py --- connection.py | 348 -------------------------------------------------- 1 file changed, 348 deletions(-) delete mode 100644 connection.py diff --git a/connection.py b/connection.py deleted file mode 100644 index cb512f5..0000000 --- a/connection.py +++ /dev/null @@ -1,348 +0,0 @@ -# -*- coding: utf-8 -*- - -''' -Copyright 2012-2019 eBay Inc. -Authored by: Tim Keefer -Licensed under CDDL 1.0 -''' - -from ebaysdk import log - -import re -import time -import uuid -import webbrowser - -from requests import Request, Session, post -from requests.adapters import HTTPAdapter - -from xml.dom.minidom import parseString -from xml.parsers.expat import ExpatError - -from ebaysdk import set_stream_logger, UserAgent -from ebaysdk.utils import getNodeText as getNodeTextUtils, smart_encode, smart_decode -from ebaysdk.utils import getValue, smart_encode_request_data -from ebaysdk.response import Response -from ebaysdk.exception import ConnectionError, ConnectionResponseError - -HTTP_SSL = { - False: 'http', - True: 'https', -} - - -class BaseConnection(object): - """Base Connection Class.""" - - def __init__(self, debug=False, method='GET', - proxy_host=None, timeout=20, proxy_port=80, - parallel=None, escape_xml=False, **kwargs): - - if debug: - set_stream_logger() - - self.response = None - self.request = None - self.verb = None - self.config = None - self.debug = debug - self.method = method - self.timeout = timeout - self.proxy_host = proxy_host - self.proxy_port = proxy_port - self.escape_xml = escape_xml - self.datetime_nodes = [] - self._list_nodes = [] - - self.proxies = dict() - if self.proxy_host: - proxy = 'http://%s:%s' % (self.proxy_host, self.proxy_port) - self.proxies = { - 'http': proxy, - 'https': proxy - } - - self.session = Session() - self.session.mount('http://', HTTPAdapter(max_retries=3)) - self.session.mount('https://', HTTPAdapter(max_retries=3)) - - self.parallel = parallel - - self.base_list_nodes = [] - self.datetime_nodes = [] - - self._reset() - - def debug_callback(self, debug_type, debug_message): - log.debug('type: ' + str(debug_type) + ' message' + str(debug_message)) - - def v(self, *args, **kwargs): - return getValue(self.response.dict(), *args, **kwargs) - - def getNodeText(self, nodelist): - return getNodeTextUtils(nodelist) - - def _reset(self): - self.response = None - self.request = None - self.verb = None - self._list_nodes = [] - self._request_id = None - self._request_dict = {} - self._time = time.time() - self._response_content = None - self._response_dom = None - self._response_obj = None - self._response_soup = None - self._response_dict = None - self._response_error = None - self._resp_body_errors = [] - self._resp_body_warnings = [] - self._resp_codes = [] - - def _add_prefix(self, nodes, verb): - if verb: - for i, v in enumerate(nodes): - if not nodes[i].startswith(verb.lower()): - nodes[i] = "%sresponse.%s" % ( - verb.lower(), nodes[i].lower()) - - def execute(self, verb, data=None, list_nodes=[], verb_attrs=None, files=None): - "Executes the HTTP request." - log.debug('execute: verb=%s data=%s' % (verb, data)) - - self._reset() - - self._list_nodes += list_nodes - self._add_prefix(self._list_nodes, verb) - - if hasattr(self, 'base_list_nodes'): - self._list_nodes += self.base_list_nodes - - self.build_request(verb, data, verb_attrs, files) - self.execute_request() - - if hasattr(self.response, 'content'): - self.process_response() - self.error_check() - - log.debug('total time=%s' % (time.time() - self._time)) - - return self.response - - def build_request(self, verb, data, verb_attrs, files=None): - - self.verb = verb - self._request_dict = data - self._request_id = uuid.uuid4() - - url = self.build_request_url(verb) - - headers = self.build_request_headers(verb) - headers.update({'User-Agent': UserAgent, - 'X-EBAY-SDK-REQUEST-ID': str(self._request_id)}) - - # if we are adding files, we ensure there is no Content-Type header already defined - # otherwise Request will use the existing one which is likely not to be multipart/form-data - # data must also be a dict so we make it so if needed - - requestData = self.build_request_data(verb, data, verb_attrs) - if files: - del(headers['Content-Type']) - if isinstance(requestData, str): # pylint: disable-msg=E0602 - requestData = {'XMLPayload': requestData} - - request = Request(self.method, - url, - data=smart_encode_request_data(requestData), - headers=headers, - files=files, - ) - - self.request = request.prepare() - - def build_request_headers(self, verb): - return {} - - def build_request_data(self, verb, data, verb_attrs): - return "" - - def build_request_url(self, verb): - url = "%s://%s%s" % ( - HTTP_SSL[self.config.get('https', True)], - self.config.get('domain'), - self.config.get('uri') - ) - return url - - def execute_request(self): - - log.debug("REQUEST (%s): %s %s" - % (self._request_id, self.request.method, self.request.url)) - log.debug('headers=%s' % self.request.headers) - log.debug('body=%s' % self.request.body) - - if self.parallel: - self.parallel._add_request(self) - return None - - self.response = self.session.send(self.request, - verify=True, - proxies=self.proxies, - timeout=self.timeout, - allow_redirects=True - ) - - log.debug('RESPONSE (%s):' % self._request_id) - log.debug('elapsed time=%s' % self.response.elapsed) - log.debug('status code=%s' % self.response.status_code) - log.debug('headers=%s' % self.response.headers) - log.debug('content=%s' % self.response.text) - - def process_response(self, parse_response=True): - """Post processing of the response""" - - self.response = Response(self.response, - verb=self.verb, - list_nodes=self._list_nodes, - datetime_nodes=self.datetime_nodes, - parse_response=parse_response) - - self.session.close() - # set for backward compatibility - self._response_content = self.response.content - - if self.response.status_code != 200: - self._response_error = self.response.reason - - def error_check(self): - estr = self.error() - - if estr and self.config.get('errors', True): - log.error(estr) - raise ConnectionError(estr, self.response) - - def response_codes(self): - return self._resp_codes - - def response_status(self): - "Retuns the HTTP response status string." - - return self.response.reason - - def response_code(self): - "Returns the HTTP response status code." - - return self.response.status_code - - def response_content(self): - return self.response.content - - def response_soup(self): - "Returns a BeautifulSoup object of the response." - - if not self._response_soup: - try: - from bs4 import BeautifulStoneSoup - except ImportError: - from BeautifulSoup import BeautifulStoneSoup - log.warning( - 'DeprecationWarning: BeautifulSoup 3 or earlier is deprecated; install bs4 instead\n') - - self._response_soup = BeautifulStoneSoup( - smart_decode(self.response_content) - ) - - return self._response_soup - - def response_obj(self): - log.warning('response_obj() DEPRECATED, use response.reply instead') - return self.response.reply - - def response_dom(self): - """ Deprecated: use self.response.dom() instead - Returns the response DOM (xml.dom.minidom). - """ - log.warning('response_dom() DEPRECATED, use response.dom instead') - - if not self._response_dom: - dom = None - content = None - - try: - if self.response.content: - regex = re.compile(b'xmlns="[^"]+"') - content = regex.sub(b'', self.response.content) - else: - content = "<%sResponse>" % ( - self.verb, self.verb) - - dom = parseString(content) - self._response_dom = dom.getElementsByTagName( - self.verb + 'Response')[0] - - except ExpatError as e: - raise ConnectionResponseError( - "Invalid Verb: %s (%s)" % (self.verb, e), self.response) - except IndexError: - self._response_dom = dom - - return self._response_dom - - def response_dict(self): - "Returns the response dictionary." - log.warning( - 'response_dict() DEPRECATED, use response.dict() or response.reply instead') - - return self.response.reply - - def response_json(self): - "Returns the response JSON." - log.warning('response_json() DEPRECATED, use response.json() instead') - - return self.response.json() - - def _get_resp_body_errors(self): - """Parses the response content to pull errors. - - Child classes should override this method based on what the errors in the - XML response body look like. They can choose to look at the 'ack', - 'Errors', 'errorMessage' or whatever other fields the service returns. - the implementation below is the original code that was part of error() - """ - - if self._resp_body_errors and len(self._resp_body_errors) > 0: - return self._resp_body_errors - - errors = [] - - if self.verb is None: - return errors - - dom = self.response.dom() - if dom is None: - return errors - - return [] - - def error(self): - "Builds and returns the api error message." - - error_array = [] - if self._response_error: - error_array.append(self._response_error) - - error_array.extend(self._get_resp_body_errors()) - - if len(error_array) > 0: - # Force all errors to be unicode in a proper way - error_array = [smart_decode(smart_encode(e)) for e in error_array] - error_string = u"{verb}: {message}".format( - verb=self.verb, message=u", ".join(error_array)) - - return error_string - - return None - - def opendoc(self): - webbrowser.open(self.config.get('doc_url')) From b1dd06044b8e3c81689303da4c13462447b574de Mon Sep 17 00:00:00 2001 From: xjxckk <17519346+xjxckk@users.noreply.github.com> Date: Mon, 28 Mar 2022 17:25:33 +0100 Subject: [PATCH 6/9] Add files via upload --- ebaysdk/connection.py | 34 +--------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index 04cde1e..cb512f5 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -24,12 +24,6 @@ from ebaysdk.utils import getValue, smart_encode_request_data from ebaysdk.response import Response from ebaysdk.exception import ConnectionError, ConnectionResponseError -from ebaysdk.config import Config -from base64 import b64encode - -from requests.structures import CaseInsensitiveDict -import json -import datetime HTTP_SSL = { False: 'http', @@ -46,22 +40,11 @@ def __init__(self, debug=False, method='GET', if debug: set_stream_logger() - - self.config = Config(domain=kwargs.get('domain', 'open.api.ebay.com'), connection_kwargs=kwargs, config_file=kwargs.get('config_file', 'ebay.yaml')) - self.time_of_last_token = datetime.datetime.min - - # Building OAuth token - client_id = self.config.get('appid', '') - client_secret = self.config.get('certid', '') - token_string = f'{client_id}:{client_secret}' - token_bytes = token_string.encode('ascii') - token_base64 = b64encode(token_bytes) - self.final_token = token_base64.decode('ascii') self.response = None self.request = None self.verb = None - # self.config = None + self.config = None self.debug = debug self.method = method self.timeout = timeout @@ -176,21 +159,6 @@ def build_request(self, verb, data, verb_attrs, files=None): files=files, ) - time_now = datetime.datetime.now() - time_difference_last_token = time_now - self.time_of_last_token - if time_difference_last_token.total_seconds() > 3600: - url = 'https://api.ebay.com/identity/v1/oauth2/token' - headers = CaseInsensitiveDict() - headers['Content-Type'] = 'application/x-www-form-urlencoded' - headers['Authorization'] = f'Basic {self.final_token}' - data = 'grant_type=client_credentials&scope=https%3A%2F%2Fapi.ebay.com%2Foauth%2Fapi_scope' - response = post(url, headers=headers, data=data) - response_dict = response.json() - access_token = response_dict['access_token'] - request.headers['X-EBAY-API-IAF-TOKEN'] = access_token - - self.time_of_last_token = time_now - self.request = request.prepare() def build_request_headers(self, verb): From 818bf083dd3ed470bd00385caf3fa0159942ede3 Mon Sep 17 00:00:00 2001 From: xjxckk <17519346+xjxckk@users.noreply.github.com> Date: Mon, 28 Mar 2022 17:25:59 +0100 Subject: [PATCH 7/9] Add files via upload --- ebaysdk/shopping/__init__.py | 48 +++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/ebaysdk/shopping/__init__.py b/ebaysdk/shopping/__init__.py index bb01854..4e8aa81 100644 --- a/ebaysdk/shopping/__init__.py +++ b/ebaysdk/shopping/__init__.py @@ -13,6 +13,13 @@ from ebaysdk.config import Config from ebaysdk.utils import getNodeText, dict2xml +## Added this / https://github.com/timotheus/ebaysdk-python/issues/347 +from base64 import b64encode + +from requests.structures import CaseInsensitiveDict +import json +import datetime + class Connection(BaseConnection): """Shopping API class @@ -66,6 +73,18 @@ def __init__(self, **kwargs): self.config = Config(domain=kwargs.get('domain', 'open.api.ebay.com'), connection_kwargs=kwargs, config_file=kwargs.get('config_file', 'ebay.yaml')) + + ## Added this + self.time_of_last_token = datetime.datetime.min + + # Building OAuth token + client_id = self.config.get('appid', '') + client_secret = self.config.get('certid', '') + token_string = f'{client_id}:{client_secret}' + token_bytes = token_string.encode('ascii') + token_base64 = b64encode(token_bytes) + self.final_token = token_base64.decode('ascii') + ## End of changes # override yaml defaults with args sent to the constructor self.config.set('domain', kwargs.get('domain', 'open.api.ebay.com')) @@ -79,7 +98,6 @@ def __init__(self, **kwargs): self.config.set('proxy_host', None) self.config.set('proxy_port', None) self.config.set('appid', None) - self.config.set('iaf_token', None) self.config.set('version', '799') self.config.set('trackingid', None) self.config.set('trackingpartnercode', None) @@ -146,10 +164,28 @@ def build_request_headers(self, verb): headers.update({ "X-EBAY-API-TRACKING-PARTNER-CODE": self.config.get('trackingpartnercode') }) - - if self.config.get('iaf_token', None): - headers["X-EBAY-API-IAF-TOKEN"] = self.config.get('iaf_token') - + + ## Added this / https://github.com/timotheus/ebaysdk-python/issues/347 + + time_now = datetime.datetime.now() + time_difference_last_token = time_now - self.time_of_last_token + # print('Time difference last token', time_difference_last_token) + if time_difference_last_token.total_seconds() > 3600: + url = 'https://api.ebay.com/identity/v1/oauth2/token' + headers = CaseInsensitiveDict() + headers['Content-Type'] = 'application/x-www-form-urlencoded' + headers['Authorization'] = f'Basic {self.final_token}' + data = 'grant_type=client_credentials&scope=https%3A%2F%2Fapi.ebay.com%2Foauth%2Fapi_scope' + response = post(url, headers=headers, data=data) + response_dict = response.json() + print('Oauth2.0 token request response:', json.dumps(response_dict, indent=4)) + access_token = response_dict['access_token'] + # previous_access_token = access_token + request.headers['X-EBAY-API-IAF-TOKEN'] = access_token + + self.time_of_last_token = time_now + + ## End of changes return headers @@ -249,7 +285,7 @@ def _get_resp_body_errors(self): self._resp_codes = resp_codes if self.config.get('warnings') and len(warnings) > 0: - log.warning("%s: %s\n\n" % (self.verb, "\n".join(warnings))) + log.warn("%s: %s\n\n" % (self.verb, "\n".join(warnings))) if self.response.reply.Ack == 'Failure': if self.config.get('errors'): From 4c0c41fd87a5bf368aa0f33769aa4a3622e37eda Mon Sep 17 00:00:00 2001 From: xjxckk <17519346+xjxckk@users.noreply.github.com> Date: Fri, 13 May 2022 11:11:23 +0100 Subject: [PATCH 8/9] Update __init__.py --- ebaysdk/shopping/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ebaysdk/shopping/__init__.py b/ebaysdk/shopping/__init__.py index 4e8aa81..9561495 100644 --- a/ebaysdk/shopping/__init__.py +++ b/ebaysdk/shopping/__init__.py @@ -19,6 +19,7 @@ from requests.structures import CaseInsensitiveDict import json import datetime +import requests class Connection(BaseConnection): @@ -176,7 +177,7 @@ def build_request_headers(self, verb): headers['Content-Type'] = 'application/x-www-form-urlencoded' headers['Authorization'] = f'Basic {self.final_token}' data = 'grant_type=client_credentials&scope=https%3A%2F%2Fapi.ebay.com%2Foauth%2Fapi_scope' - response = post(url, headers=headers, data=data) + response = requests.post(url, headers=headers, data=data) response_dict = response.json() print('Oauth2.0 token request response:', json.dumps(response_dict, indent=4)) access_token = response_dict['access_token'] From 5e8f5c9e3bc5d64157fec7077396d797ec09de4d Mon Sep 17 00:00:00 2001 From: xjxckk <17519346+xjxckk@users.noreply.github.com> Date: Fri, 13 May 2022 11:11:30 +0100 Subject: [PATCH 9/9] Update connection.py --- ebaysdk/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ebaysdk/connection.py b/ebaysdk/connection.py index cb512f5..a29a644 100644 --- a/ebaysdk/connection.py +++ b/ebaysdk/connection.py @@ -13,7 +13,7 @@ import uuid import webbrowser -from requests import Request, Session, post +from requests import Request, Session from requests.adapters import HTTPAdapter from xml.dom.minidom import parseString