Package fedex :: Module base_service
[hide private]
[frames] | no frames]

Source Code for Module fedex.base_service

  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   
18 -class GeneralSudsPlugin(MessagePlugin):
19 - def __init__(self, **kwargs):
20 self.request_logger = logging.getLogger('fedex.request') 21 self.response_logger = logging.getLogger('fedex.response') 22 self.kwargs = kwargs
23
24 - def marshalled(self, context):
25 # Removes the WSDL objects that do not have a value before sending. 26 context.envelope = context.envelope.prune()
27
28 - def sending(self, context):
29 self.request_logger.info("FedEx Request {}".format(context.envelope))
30
31 - def received(self, context):
32 self.response_logger.info("FedEx Response {}".format(context.reply))
33 34
35 -class FedexBaseServiceException(Exception):
36 """ 37 Exception: Serves as the base exception that other service-related 38 exception objects are sub-classed from. 39 """ 40
41 - def __init__(self, error_code, value):
42 self.error_code = error_code 43 self.value = value
44
45 - def __unicode__(self):
46 return "%s (Error code: %s)" % (repr(self.value), self.error_code)
47
48 - def __str__(self):
49 return self.__unicode__()
50 51
52 -class FedexFailure(FedexBaseServiceException):
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
61 -class FedexError(FedexBaseServiceException):
62 """ 63 Exception: These are generally problems with the client-provided data. 64 """ 65 66 pass
67 68
69 -class SchemaValidationError(FedexBaseServiceException):
70 """ 71 Exception: There is probably a problem in the data you provided. 72 """ 73
74 - def __init__(self, fault):
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
84 -class FedexBaseService(object):
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 # If the config object is set to use the test server, point 118 # suds at the test server WSDL directory. 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 # self.client.options.cache.clear() # Clear the cache, then re-init client when changing wsdl file. 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 # Start of the authentication stuff. 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 # Encapsulates the auth credentials. 162 web_authentication_detail = self.client.factory.create('WebAuthenticationDetail') 163 web_authentication_detail.UserCredential = web_authentication_credential 164 165 # Set Default ParentCredential 166 if hasattr(web_authentication_detail, 'ParentCredential'): 167 web_authentication_detail.ParentCredential = web_authentication_credential 168 169 self.WebAuthenticationDetail = web_authentication_detail
170
171 - def __set_client_detail(self, *args, **kwargs):
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
200 - def __set_transaction_detail(self, *args, **kwargs):
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
212 - def __set_version_id(self):
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
225 - def _prepare_wsdl_objects(self):
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
272 - def create_wsdl_object_of_type(self, type_name):
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
291 - def send_request(self, send_function=None):
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 # Send the request and get the response back. 302 try: 303 # If the user has overridden the send function, use theirs 304 # instead of the default. 305 if send_function: 306 # Follow the overridden function. 307 self.response = send_function() 308 else: 309 # Default scenario, business as usual. 310 self.response = self._assemble_and_send_request() 311 except suds.WebFault as fault: 312 # When this happens, throw an informative message reminding the 313 # user to check all required variables, making sure they are 314 # populated and valid 315 raise SchemaValidationError(fault.fault) 316 317 # Check the response for general Fedex errors/failures that aren't 318 # specific to any given WSDL/request. 319 self.__check_response_for_fedex_error() 320 321 # Check the response for errors specific to the particular request. 322 # This method can be overridden by a method on the child class object. 323 self._check_response_for_request_errors() 324 325 # Check the response for errors specific to the particular request. 326 # This method can be overridden by a method on the child class object. 327 self._check_response_for_request_warnings() 328 329 # Debug output. 330 self.logger.debug("== FEDEX QUERY RESULT ==") 331 self.logger.debug(self.response)
332