1 """
2 The L{base_service} module contains classes that form the low level foundations
3 of the Web Service API. Things that many different kinds of requests have in
4 common may be found here.
5
6 In particular, the L{FedexBaseService} class handles most of the basic,
7 repetitive setup work that most requests do.
8 """
9
10 import os
11 import logging
12
13 import suds
14 from suds.client import Client
15 from suds.plugin import MessagePlugin
16
17
20 self.request_logger = logging.getLogger('fedex.request')
21 self.response_logger = logging.getLogger('fedex.response')
22 self.kwargs = kwargs
23
25
26 context.envelope = context.envelope.prune()
27
29 self.request_logger.info("FedEx Request {}".format(context.envelope))
30
32 self.response_logger.info("FedEx Response {}".format(context.reply))
33
34
36 """
37 Exception: Serves as the base exception that other service-related
38 exception objects are sub-classed from.
39 """
40
42 self.error_code = error_code
43 self.value = value
44
46 return "%s (Error code: %s)" % (repr(self.value), self.error_code)
47
50
51
53 """
54 Exception: The request could not be handled at this time. This is generally
55 a server problem.
56 """
57
58 pass
59
60
62 """
63 Exception: These are generally problems with the client-provided data.
64 """
65
66 pass
67
68
70 """
71 Exception: There is probably a problem in the data you provided.
72 """
73
75 self.error_code = -1
76 self.value = "suds encountered an error validating your data against this service's WSDL schema. " \
77 "Please double-check for missing or invalid values, filling all required fields."
78 try:
79 self.value += ' Details: {}'.format(fault)
80 except AttributeError:
81 pass
82
83
85 """
86 This class is the master class for all Fedex request objects. It gets all
87 of the common SOAP objects created via suds and populates them with
88 values from a L{FedexConfig} object, along with keyword arguments
89 via L{__init__}.
90
91 @note: This object should never be used directly, use one of the included
92 sub-classes.
93 """
94
95 - def __init__(self, config_obj, wsdl_name, *args, **kwargs):
96 """
97 This constructor should only be called by children of the class. As is
98 such, only the optional keyword arguments caught by C{**kwargs} will
99 be documented.
100
101 @type customer_transaction_id: L{str}
102 @keyword customer_transaction_id: A user-specified identifier to
103 differentiate this transaction from others. This value will be
104 returned with the response from Fedex.
105 """
106
107 self.logger = logging.getLogger('fedex')
108 """@ivar: Python logger instance with name 'fedex'."""
109
110 self.config_obj = config_obj
111 """@ivar: The FedexConfig object to pull auth info from."""
112
113 if not self._version_info:
114 self._version_info = {}
115 """#ivar: Set in each service class. Holds version info for the VersionId SOAP object."""
116
117
118
119 if config_obj.use_test_server:
120 self.logger.info("Using test server.")
121 self.wsdl_path = os.path.join(config_obj.wsdl_path,
122 'test_server_wsdl', wsdl_name)
123 else:
124 self.logger.info("Using production server.")
125 self.wsdl_path = os.path.join(config_obj.wsdl_path, wsdl_name)
126
127 self.client = Client('file:///%s' % self.wsdl_path.lstrip('/'), plugins=[GeneralSudsPlugin()])
128
129
130 self.VersionId = None
131 """@ivar: Holds details on the version numbers of the WSDL."""
132 self.WebAuthenticationDetail = None
133 """@ivar: WSDL object that holds authentication info."""
134 self.ClientDetail = None
135 """@ivar: WSDL object that holds client account details."""
136 self.response = None
137 """@ivar: The response from Fedex. You will want to pick what you
138 want out here here. This object does have a __str__() method,
139 you'll want to print or log it to see what possible values
140 you can pull."""
141 self.TransactionDetail = None
142 """@ivar: Holds customer-specified transaction IDs."""
143
144 self.__set_web_authentication_detail()
145 self.__set_client_detail(*args, **kwargs)
146 self.__set_version_id()
147 self.__set_transaction_detail(*args, **kwargs)
148 self._prepare_wsdl_objects()
149
151 """
152 Sets up the WebAuthenticationDetail node. This is required for all
153 requests.
154 """
155
156
157 web_authentication_credential = self.client.factory.create('WebAuthenticationCredential')
158 web_authentication_credential.Key = self.config_obj.key
159 web_authentication_credential.Password = self.config_obj.password
160
161
162 web_authentication_detail = self.client.factory.create('WebAuthenticationDetail')
163 web_authentication_detail.UserCredential = web_authentication_credential
164
165
166 if hasattr(web_authentication_detail, 'ParentCredential'):
167 web_authentication_detail.ParentCredential = web_authentication_credential
168
169 self.WebAuthenticationDetail = web_authentication_detail
170
172 """
173 Sets up the ClientDetail node, which is required for all shipping
174 related requests.
175 """
176
177 client_detail = self.client.factory.create('ClientDetail')
178 client_detail.AccountNumber = self.config_obj.account_number
179 client_detail.MeterNumber = self.config_obj.meter_number
180 client_detail.IntegratorId = self.config_obj.integrator_id
181 if hasattr(client_detail, 'Region'):
182 client_detail.Region = self.config_obj.express_region_code
183
184 client_language_code = kwargs.get('client_language_code', None)
185 client_locale_code = kwargs.get('client_locale_code', None)
186
187 if hasattr(client_detail, 'Localization') and (client_language_code or client_locale_code):
188 localization = self.client.factory.create('Localization')
189
190 if client_language_code:
191 localization.LanguageCode = client_language_code
192
193 if client_locale_code:
194 localization.LocaleCode = client_locale_code
195
196 client_detail.Localization = localization
197
198 self.ClientDetail = client_detail
199
201 """
202 Checks kwargs for 'customer_transaction_id' and sets it if present.
203 """
204
205 customer_transaction_id = kwargs.get('customer_transaction_id', None)
206 if customer_transaction_id:
207 transaction_detail = self.client.factory.create('TransactionDetail')
208 transaction_detail.CustomerTransactionId = customer_transaction_id
209 self.logger.debug(transaction_detail)
210 self.TransactionDetail = transaction_detail
211
213 """
214 Pulles the versioning info for the request from the child request.
215 """
216
217 version_id = self.client.factory.create('VersionId')
218 version_id.ServiceId = self._version_info['service_id']
219 version_id.Major = self._version_info['major']
220 version_id.Intermediate = self._version_info['intermediate']
221 version_id.Minor = self._version_info['minor']
222 self.logger.debug(version_id)
223 self.VersionId = version_id
224
226 """
227 This method should be over-ridden on each sub-class. It instantiates
228 any of the required WSDL objects so the user can just print their
229 __str__() methods and see what they need to fill in.
230 """
231
232 pass
233
235 """
236 This checks the response for general Fedex errors that aren't related
237 to any one WSDL.
238 """
239
240 if self.response.HighestSeverity == "FAILURE":
241 for notification in self.response.Notifications:
242 if notification.Severity == "FAILURE":
243 raise FedexFailure(notification.Code,
244 notification.Message)
245
247 """
248 Override this in each service module to check for errors that are
249 specific to that module. For example, invalid tracking numbers in
250 a Tracking request.
251 """
252
253 if self.response.HighestSeverity == "ERROR":
254 for notification in self.response.Notifications:
255 if notification.Severity == "ERROR":
256 raise FedexError(notification.Code,
257 notification.Message)
258
260 """
261 Override this in a service module to check for errors that are
262 specific to that module. For example, changing state/province based
263 on postal code in a Rate Service request.
264 """
265
266 if self.response.HighestSeverity == "NOTE":
267 for notification in self.response.Notifications:
268 if notification.Severity == "NOTE":
269 self.logger.warning(FedexFailure(notification.Code,
270 notification.Message))
271
273 """
274 Creates and returns a WSDL object of the specified type.
275 :param type_name: specifies the object's type name from WSDL.
276 """
277
278 return self.client.factory.create(type_name)
279
281 """
282 This method should be over-ridden on each sub-class.
283 It assembles all required objects
284 into the specific request object and calls send_request.
285 Objects that are not set will be pruned before sending
286 via GeneralSudsPlugin marshalled function.
287 """
288
289 pass
290
292 """
293 Sends the assembled request on the child object.
294 @type send_function: function reference
295 @keyword send_function: A function reference (passed without the
296 parenthesis) to a function that will send the request. This
297 allows for overriding the default function in cases such as
298 validation requests.
299 """
300
301
302 try:
303
304
305 if send_function:
306
307 self.response = send_function()
308 else:
309
310 self.response = self._assemble_and_send_request()
311 except suds.WebFault as fault:
312
313
314
315 raise SchemaValidationError(fault.fault)
316
317
318
319 self.__check_response_for_fedex_error()
320
321
322
323 self._check_response_for_request_errors()
324
325
326
327 self._check_response_for_request_warnings()
328
329
330 self.logger.debug("== FEDEX QUERY RESULT ==")
331 self.logger.debug(self.response)
332