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.kwargs = kwargs
21
22 - def marshalled(self, context):
23 context.envelope = context.envelope.prune()
24
25 - def sending(self, context):
26 logging.info("FedEx Request {}".format(context.envelope))
27
28 - def received(self, context):
29 logging.info("FedEx Response {}".format(context.reply))
30 31
32 -class FedexBaseServiceException(Exception):
33 """ 34 Exception: Serves as the base exception that other service-related 35 exception objects are sub-classed from. 36 """ 37
38 - def __init__(self, error_code, value):
39 self.error_code = error_code 40 self.value = value
41
42 - def __unicode__(self):
43 return "%s (Error code: %s)" % (repr(self.value), self.error_code)
44
45 - def __str__(self):
46 return self.__unicode__()
47 48
49 -class FedexFailure(FedexBaseServiceException):
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
58 -class FedexError(FedexBaseServiceException):
59 """ 60 Exception: These are generally problems with the client-provided data. 61 """ 62 63 pass
64 65
66 -class SchemaValidationError(FedexBaseServiceException):
67 """ 68 Exception: There is probably a problem in the data you provided. 69 """ 70
71 - def __init__(self, fault):
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
81 -class FedexBaseService(object):
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 # If the config object is set to use the test server, point 110 # suds at the test server WSDL directory. 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 # self.client.options.cache.clear() # Clear the cache, then re-init client when changing wsdl file. 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 # Start of the authentication stuff. 149 WebAuthenticationCredential = self.client.factory.create('WebAuthenticationCredential') 150 WebAuthenticationCredential.Key = self.config_obj.key 151 WebAuthenticationCredential.Password = self.config_obj.password 152 153 # Encapsulates the auth credentials. 154 WebAuthenticationDetail = self.client.factory.create('WebAuthenticationDetail') 155 WebAuthenticationDetail.UserCredential = WebAuthenticationCredential 156 157 # Set Default ParentCredential 158 if hasattr(WebAuthenticationDetail, 'ParentCredential'): 159 WebAuthenticationDetail.ParentCredential = WebAuthenticationCredential 160 161 self.WebAuthenticationDetail = WebAuthenticationDetail
162
163 - def __set_client_detail(self, *args, **kwargs):
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
192 - def __set_transaction_detail(self, *args, **kwargs):
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
204 - def __set_version_id(self):
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
217 - def _prepare_wsdl_objects(self):
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
251 - def create_wsdl_object_of_type(self, type_name):
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
266 - def send_request(self, send_function=None):
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 # Send the request and get the response back. 277 try: 278 # If the user has overridden the send function, use theirs 279 # instead of the default. 280 if send_function: 281 # Follow the overridden function. 282 self.response = send_function() 283 else: 284 # Default scenario, business as usual. 285 self.response = self._assemble_and_send_request() 286 except suds.WebFault as fault: 287 # When this happens, throw an informative message reminding the 288 # user to check all required variables, making sure they are 289 # populated and valid 290 raise SchemaValidationError(fault.fault) 291 292 # Check the response for general Fedex errors/failures that aren't 293 # specific to any given WSDL/request. 294 self.__check_response_for_fedex_error() 295 # Check the response for errors specific to the particular request. 296 # This is handled by an overridden method on the child object. 297 self._check_response_for_request_errors() 298 299 # Debug output. 300 self.logger.debug("== FEDEX QUERY RESULT ==") 301 self.logger.debug(self.response)
302