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
21
23 context.envelope = context.envelope.prune()
24
26 logging.info("FedEx Request {}".format(context.envelope))
27
29 logging.info("FedEx Response {}".format(context.reply))
30
31
33 """
34 Exception: Serves as the base exception that other service-related
35 exception objects are sub-classed from.
36 """
37
39 self.error_code = error_code
40 self.value = value
41
43 return "%s (Error code: %s)" % (repr(self.value), self.error_code)
44
47
48
50 """
51 Exception: The request could not be handled at this time. This is generally
52 a server problem.
53 """
54
55 pass
56
57
59 """
60 Exception: These are generally problems with the client-provided data.
61 """
62
63 pass
64
65
67 """
68 Exception: There is probably a problem in the data you provided.
69 """
70
72 self.error_code = -1
73 self.value = "suds encountered an error validating your data against this service's WSDL schema. " \
74 "Please double-check for missing or invalid values, filling all required fields."
75 try:
76 self.value += ' Details: {}'.format(fault)
77 except AttributeError:
78 pass
79
80
82 """
83 This class is the master class for all Fedex request objects. It gets all
84 of the common SOAP objects created via suds and populates them with
85 values from a L{FedexConfig} object, along with keyword arguments
86 via L{__init__}.
87
88 @note: This object should never be used directly, use one of the included
89 sub-classes.
90 """
91
92 - def __init__(self, config_obj, wsdl_name, *args, **kwargs):
93 """
94 This constructor should only be called by children of the class. As is
95 such, only the optional keyword arguments caught by C{**kwargs} will
96 be documented.
97
98 @type customer_transaction_id: L{str}
99 @keyword customer_transaction_id: A user-specified identifier to
100 differentiate this transaction from others. This value will be
101 returned with the response from Fedex.
102 """
103
104 self.logger = logging.getLogger('fedex')
105 """@ivar: Python logger instance with name 'fedex'."""
106 self.config_obj = config_obj
107 """@ivar: The FedexConfig object to pull auth info from."""
108
109
110
111 if config_obj.use_test_server:
112 self.logger.info("Using test server.")
113 self.wsdl_path = os.path.join(config_obj.wsdl_path,
114 'test_server_wsdl', wsdl_name)
115 else:
116 self.logger.info("Using production server.")
117 self.wsdl_path = os.path.join(config_obj.wsdl_path, wsdl_name)
118
119 self.client = Client('file:///%s' % self.wsdl_path.lstrip('/'), plugins=[GeneralSudsPlugin()])
120
121
122 self.VersionId = None
123 """@ivar: Holds details on the version numbers of the WSDL."""
124 self.WebAuthenticationDetail = None
125 """@ivar: WSDL object that holds authentication info."""
126 self.ClientDetail = None
127 """@ivar: WSDL object that holds client account details."""
128 self.response = None
129 """@ivar: The response from Fedex. You will want to pick what you
130 want out here here. This object does have a __str__() method,
131 you'll want to print or log it to see what possible values
132 you can pull."""
133 self.TransactionDetail = None
134 """@ivar: Holds customer-specified transaction IDs."""
135
136 self.__set_web_authentication_detail()
137 self.__set_client_detail(*args, **kwargs)
138 self.__set_version_id()
139 self.__set_transaction_detail(*args, **kwargs)
140 self._prepare_wsdl_objects()
141
143 """
144 Sets up the WebAuthenticationDetail node. This is required for all
145 requests.
146 """
147
148
149 WebAuthenticationCredential = self.client.factory.create('WebAuthenticationCredential')
150 WebAuthenticationCredential.Key = self.config_obj.key
151 WebAuthenticationCredential.Password = self.config_obj.password
152
153
154 WebAuthenticationDetail = self.client.factory.create('WebAuthenticationDetail')
155 WebAuthenticationDetail.UserCredential = WebAuthenticationCredential
156
157
158 if hasattr(WebAuthenticationDetail, 'ParentCredential'):
159 WebAuthenticationDetail.ParentCredential = WebAuthenticationCredential
160
161 self.WebAuthenticationDetail = WebAuthenticationDetail
162
164 """
165 Sets up the ClientDetail node, which is required for all shipping
166 related requests.
167 """
168
169 ClientDetail = self.client.factory.create('ClientDetail')
170 ClientDetail.AccountNumber = self.config_obj.account_number
171 ClientDetail.MeterNumber = self.config_obj.meter_number
172 ClientDetail.IntegratorId = self.config_obj.integrator_id
173 if hasattr(ClientDetail, 'Region'):
174 ClientDetail.Region = self.config_obj.express_region_code
175
176 client_language_code = kwargs.get('client_language_code', None)
177 client_locale_code = kwargs.get('client_locale_code', None)
178
179 if hasattr(ClientDetail, 'Localization') and (client_language_code or client_locale_code):
180 Localization = self.client.factory.create('Localization')
181
182 if client_language_code:
183 Localization.LanguageCode = client_language_code
184
185 if client_locale_code:
186 Localization.LocaleCode = client_locale_code
187
188 ClientDetail.Localization = Localization
189
190 self.ClientDetail = ClientDetail
191
193 """
194 Checks kwargs for 'customer_transaction_id' and sets it if present.
195 """
196
197 customer_transaction_id = kwargs.get('customer_transaction_id', None)
198 if customer_transaction_id:
199 TransactionDetail = self.client.factory.create('TransactionDetail')
200 TransactionDetail.CustomerTransactionId = customer_transaction_id
201 self.logger.debug(TransactionDetail)
202 self.TransactionDetail = TransactionDetail
203
205 """
206 Pulles the versioning info for the request from the child request.
207 """
208
209 VersionId = self.client.factory.create('VersionId')
210 VersionId.ServiceId = self._version_info['service_id']
211 VersionId.Major = self._version_info['major']
212 VersionId.Intermediate = self._version_info['intermediate']
213 VersionId.Minor = self._version_info['minor']
214 self.logger.debug(VersionId)
215 self.VersionId = VersionId
216
218 """
219 This method should be over-ridden on each sub-class. It instantiates
220 any of the required WSDL objects so the user can just print their
221 __str__() methods and see what they need to fill in.
222 """
223
224 pass
225
227 """
228 This checks the response for general Fedex errors that aren't related
229 to any one WSDL.
230 """
231
232 if self.response.HighestSeverity == "FAILURE":
233 for notification in self.response.Notifications:
234 if notification.Severity == "FAILURE":
235 raise FedexFailure(notification.Code,
236 notification.Message)
237
239 """
240 Override this in each service module to check for errors that are
241 specific to that module. For example, invalid tracking numbers in
242 a Tracking request.
243 """
244
245 if self.response.HighestSeverity == "ERROR":
246 for notification in self.response.Notifications:
247 if notification.Severity == "ERROR":
248 raise FedexError(notification.Code,
249 notification.Message)
250
252 """
253 Creates and returns a WSDL object of the specified type.
254 """
255
256 return self.client.factory.create(type_name)
257
259 """
260 This method should be over-ridden on each sub-class. It assembles all required objects
261 into the specific request object and calls send_request.
262 """
263
264 pass
265
267 """
268 Sends the assembled request on the child object.
269 @type send_function: function reference
270 @keyword send_function: A function reference (passed without the
271 parenthesis) to a function that will send the request. This
272 allows for overriding the default function in cases such as
273 validation requests.
274 """
275
276
277 try:
278
279
280 if send_function:
281
282 self.response = send_function()
283 else:
284
285 self.response = self._assemble_and_send_request()
286 except suds.WebFault as fault:
287
288
289
290 raise SchemaValidationError(fault.fault)
291
292
293
294 self.__check_response_for_fedex_error()
295
296
297 self._check_response_for_request_errors()
298
299
300 self.logger.debug("== FEDEX QUERY RESULT ==")
301 self.logger.debug(self.response)
302