1# -*- coding: utf-8 -*-
2
3'''
4Copyright 2012-2019 eBay Inc.
5Authored by: Tim Keefer
6Licensed under CDDL 1.0
7'''
8
9from ebaysdk import log
10from ebaysdk.connection import BaseConnection
11from ebaysdk.config import Config
12from ebaysdk.utils import getNodeText, dict2xml
13
14
15class Connection(BaseConnection):
16    """Connection class for a base SOA service"""
17
18    def __init__(self, app_config=None, site_id='EBAY-US', debug=False, **kwargs):
19        """SOA Connection class constructor"""
20
21        super(Connection, self).__init__(method='POST', debug=debug, **kwargs)
22
23        self.config = Config(domain=kwargs.get('domain', ''),
24                             connection_kwargs=kwargs,
25                             config_file=kwargs.get('config_file', 'ebay.yaml'))
26
27        self.config.set('https', False)
28        self.config.set('site_id', site_id)
29        self.config.set('content_type', 'text/xml;charset=UTF-8')
30        self.config.set('request_encoding', 'XML')
31        self.config.set('response_encoding', 'XML')
32        self.config.set('message_protocol', 'SOAP12')
33        # http://www.ebay.com/marketplace/fundraising/v1/services',
34        self.config.set('soap_env_str', '')
35
36        ph = None
37        pp = 80
38        if app_config:
39            self.load_from_app_config(app_config)
40            ph = self.config.get('proxy_host', ph)
41            pp = self.config.get('proxy_port', pp)
42
43    # override this method, to provide setup through a config object, which
44    # should provide a get() method for extracting constants we care about
45    # this method should then set the .api_config[] dict (e.g. the comment
46    # below)
47    def load_from_app_config(self, app_config):
48        # self.api_config['domain'] = app_config.get('API_SERVICE_DOMAIN')
49        # self.api_config['uri'] = app_config.get('API_SERVICE_URI')
50        pass
51
52    # Note: this method will always return at least an empty object_dict!
53    #   It used to return None in some cases. If you get an empty dict,
54    #   you can use the .error() method to look for the cause.
55    def response_dict(self):
56        return self.response.dict()
57
58    def build_request_headers(self, verb):
59        return {
60            'Content-Type': self.config.get('content_type'),
61            'X-EBAY-SOA-SERVICE-NAME': self.config.get('service'),
62            'X-EBAY-SOA-OPERATION-NAME': verb,
63            'X-EBAY-SOA-GLOBAL-ID': self.config.get('site_id'),
64            'X-EBAY-SOA-REQUEST-DATA-FORMAT': self.config.get('request_encoding'),
65            'X-EBAY-SOA-RESPONSE-DATA-FORMAT': self.config.get('response_encoding'),
66            'X-EBAY-SOA-MESSAGE-PROTOCOL': self.config.get('message_protocol'),
67        }
68
69    def build_request_data(self, verb, data, verb_attrs):
70        xml = '<?xml version="1.0" encoding="utf-8"?>'
71        xml += '<soap:Envelope'
72        xml += ' xmlns:soap="http://www.w3.org/2003/05/soap-envelope"'
73        xml += ' xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"'
74        xml += ' xmlns:ser="%s" >' % self.config.get('soap_env_str')
75        xml += '<soap:Body>'
76        xml += '<ser:%sRequest>' % verb
77        xml += dict2xml(self.soapify(data), self.escape_xml)
78        xml += '</ser:%sRequest>' % verb
79        xml += '</soap:Body>'
80        xml += '</soap:Envelope>'
81        return xml
82
83    def soapify(self, xml):
84        xml_type = type(xml)
85        if xml_type == dict:
86            soap = {}
87            for k, v in list(xml.items()):
88                if k == '@attrs' or k == '#text':
89                    soap[k] = v
90
91                # skip nodes that have ns defined
92                elif ':' in k:
93                    soap[k] = self.soapify(v)
94                else:
95                    soap['ser:%s' % (k)] = self.soapify(v)
96
97        elif xml_type == list:
98            soap = []
99            for x in xml:
100                soap.append(self.soapify(x))
101        else:
102            soap = xml
103        return soap
104
105    def warnings(self):
106        warning_string = ""
107
108        if len(self._resp_body_warnings) > 0:
109            warning_string = "%s: %s" \
110                % (self.verb, ", ".join(self._resp_body_warnings))
111
112        return warning_string
113
114    def _get_resp_body_errors(self):
115        """Parses the response content to pull errors.
116
117        Child classes should override this method based on what the errors in the
118        XML response body look like. They can choose to look at the 'ack',
119        'Errors', 'errorMessage' or whatever other fields the service returns.
120        the implementation below is the original code that was part of error()
121
122        <errorMessage xmlns="http://www.ebay.com/marketplace/search/v1/services"><error><errorId>5014</errorId><domain>CoreRuntime</domain><severity>Error</severity><category>System</category><message>
123        """
124
125        if self._resp_body_errors and len(self._resp_body_errors) > 0:
126            return self._resp_body_errors
127
128        errors = []
129        warnings = []
130        resp_codes = []
131
132        if self.verb is None:
133            return errors
134
135        dom = self.response.dom()
136        if dom is None:
137            return errors
138
139        for e in dom.findall('error'):
140
141            eSeverity = None
142            eDomain = None
143            eMsg = None
144            eId = None
145
146            try:
147                eSeverity = e.findall('severity')[0].text
148            except IndexError:
149                pass
150
151            try:
152                eDomain = e.findall('domain')[0].text
153            except IndexError:
154                pass
155
156            try:
157                eId = e.findall('errorId')[0].text
158                if int(eId) not in resp_codes:
159                    resp_codes.append(int(eId))
160            except IndexError:
161                pass
162
163            try:
164                eMsg = e.findall('message')[0].text
165            except IndexError:
166                pass
167
168            msg = "Domain: %s, Severity: %s, errorId: %s, %s" \
169                % (eDomain, eSeverity, eId, eMsg)
170
171            if eSeverity == 'Warning':
172                warnings.append(msg)
173            else:
174                errors.append(msg)
175
176        self._resp_body_warnings = warnings
177        self._resp_body_errors = errors
178        self._resp_codes = resp_codes
179
180        if self.config.get('warnings') and len(warnings) > 0:
181            log.warn("%s: %s\n\n" % (self.verb, "\n".join(warnings)))
182
183        try:
184            if self.response.reply.ack == 'Success' and len(errors) > 0 and self.config.get('errors'):
185                log.error("%s: %s\n\n" % (self.verb, "\n".join(errors)))
186            elif len(errors) > 0:
187                if self.config.get('errors'):
188                    log.error("%s: %s\n\n" % (self.verb, "\n".join(errors)))
189                return errors
190        except AttributeError:
191            pass
192
193        return []
194