Skip to content

Commit aa7917b

Browse files
author
Greg Taylor
committed
Merging in yathib's submission of FedEx Rate Request and Postal Inquiry classes. These allow checking for prices, validity, and service availability for an arbitrary postal code. Thanks, yathib!
1 parent dbda7d5 commit aa7917b

File tree

8 files changed

+11026
-0
lines changed

8 files changed

+11026
-0
lines changed

examples/postal_inquiry.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env python
2+
"""
3+
PostalCodeInquiryRequest classes are used to validate and receive additional
4+
information about postal codes.
5+
"""
6+
import logging
7+
from example_config import CONFIG_OBJ
8+
from fedex.services.package_movement import PostalCodeInquiryRequest
9+
10+
# Set this to the INFO level to see the response from Fedex printed in stdout.
11+
logging.basicConfig(level=logging.INFO)
12+
13+
# We're using the FedexConfig object from example_config.py in this dir.
14+
inquiry = PostalCodeInquiryRequest(CONFIG_OBJ)
15+
inquiry.PostalCode = '29631'
16+
inquiry.CountryCode = 'US'
17+
18+
# Fires off the request, sets the 'response' attribute on the object.
19+
inquiry.send_request()
20+
21+
# See the response printed out.
22+
print inquiry.response

examples/rate_request.py

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/usr/bin/env python
2+
"""
3+
This example shows how to use the FedEx RateRequest service.
4+
The variables populated below represents the minimum required values.
5+
You will need to fill all of these, orrisk seeing a SchemaValidationError
6+
exception thrown by suds.
7+
8+
TIP: Near the bottom of the module, see how to check the if the destination
9+
is Out of Delivery Area (ODA).
10+
"""
11+
import logging
12+
from example_config import CONFIG_OBJ
13+
from fedex.services.rate_service import FedexRateServiceRequest
14+
15+
# Set this to the INFO level to see the response from Fedex printed in stdout.
16+
logging.basicConfig(level=logging.INFO)
17+
18+
# This is the object that will be handling our tracking request.
19+
# We're using the FedexConfig object from example_config.py in this dir.
20+
rate_request = FedexRateServiceRequest(CONFIG_OBJ)
21+
22+
# This is very generalized, top-level information.
23+
# REGULAR_PICKUP, REQUEST_COURIER, DROP_BOX, BUSINESS_SERVICE_CENTER or STATION
24+
rate_request.RequestedShipment.DropoffType = 'REGULAR_PICKUP'
25+
26+
# See page 355 in WS_ShipService.pdf for a full list. Here are the common ones:
27+
# STANDARD_OVERNIGHT, PRIORITY_OVERNIGHT, FEDEX_GROUND, FEDEX_EXPRESS_SAVER
28+
rate_request.RequestedShipment.ServiceType = 'INTERNATIONAL_PRIORITY'
29+
30+
# What kind of package this will be shipped in.
31+
# FEDEX_BOX, FEDEX_PAK, FEDEX_TUBE, YOUR_PACKAGING
32+
rate_request.RequestedShipment.PackagingType = 'YOUR_PACKAGING'
33+
34+
# No idea what this is.
35+
# INDIVIDUAL_PACKAGES, PACKAGE_GROUPS, PACKAGE_SUMMARY
36+
rate_request.RequestedShipment.PackageDetail = 'INDIVIDUAL_PACKAGES'
37+
38+
# Shipper's address
39+
rate_request.RequestedShipment.Shipper.Address.PostalCode = '29631'
40+
rate_request.RequestedShipment.Shipper.Address.CountryCode = 'US'
41+
rate_request.RequestedShipment.Shipper.Address.Residential = False
42+
43+
# Recipient address
44+
rate_request.RequestedShipment.Recipient.Address.PostalCode = '27577'
45+
rate_request.RequestedShipment.Recipient.Address.CountryCode = 'US'
46+
# This is needed to ensure an accurate rate quote with the response.
47+
#rate_request.RequestedShipment.Recipient.Address.Residential = True
48+
49+
# Who pays for the rate_request?
50+
# RECIPIENT, SENDER or THIRD_PARTY
51+
rate_request.RequestedShipment.ShippingChargesPayment.PaymentType = 'SENDER'
52+
53+
package1_weight = rate_request.create_wsdl_object_of_type('Weight')
54+
# Weight, in LB.
55+
package1_weight.Value = 1.0
56+
package1_weight.Units = "LB"
57+
58+
package1 = rate_request.create_wsdl_object_of_type('RequestedPackageLineItem')
59+
package1.Weight = package1_weight
60+
# Un-comment this to see the other variables you may set on a package.
61+
#print package1
62+
63+
# This adds the RequestedPackageLineItem WSDL object to the rate_request. It
64+
# increments the package count and total weight of the rate_request for you.
65+
rate_request.add_package(package1)
66+
67+
# If you'd like to see some documentation on the ship service WSDL, un-comment
68+
# this line. (Spammy).
69+
#print rate_request.client
70+
71+
# Un-comment this to see your complete, ready-to-send request as it stands
72+
# before it is actually sent. This is useful for seeing what values you can
73+
# change.
74+
#print rate_request.RequestedShipment
75+
76+
# Fires off the request, sets the 'response' attribute on the object.
77+
rate_request.send_request()
78+
79+
# This will show the reply to your rate_request being sent. You can access the
80+
# attributes through the response attribute on the request object. This is
81+
# good to un-comment to see the variables returned by the FedEx reply.
82+
#print rate_request.response
83+
84+
# Here is the overall end result of the query.
85+
print "HighestSeverity:", rate_request.response.HighestSeverity
86+
87+
for detail in rate_request.response.RateReplyDetails[0].RatedShipmentDetails:
88+
for surcharge in detail.ShipmentRateDetail.Surcharges:
89+
if surcharge.SurchargeType == 'OUT_OF_DELIVERY_AREA':
90+
print "ODA rate_request charge %s" % surcharge.Amount.Amount
91+
92+
for rate_detail in rate_request.response.RateReplyDetails[0].RatedShipmentDetails:
93+
print "Net FedEx Charge %s %s" % (rate_detail.ShipmentRateDetail.TotalNetFedExCharge.Currency,
94+
rate_detail.ShipmentRateDetail.TotalNetFedExCharge.Amount)
95+

fedex/services/package_movement.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""
2+
Package Movement Information Service
3+
====================================
4+
This package contains classes to check service availability, route, and postal
5+
codes. Defined by the PackageMovementInformationService WSDL file.
6+
"""
7+
import logging
8+
from .. base_service import FedexBaseService, FedexError
9+
10+
class FedexPostalCodeNotFound(FedexError):
11+
"""
12+
Sent when the postalcode is missing.
13+
"""
14+
pass
15+
16+
class FedexInvalidPostalCodeFormat(FedexError):
17+
"""
18+
Sent when the postal code is invalid
19+
"""
20+
pass
21+
22+
class PostalCodeInquiryRequest(FedexBaseService):
23+
"""
24+
The postal code inquiry enables customers to validate postal codes
25+
and service commitments.
26+
"""
27+
def __init__(self, config_obj, postal_code=None, country_code=None, *args, **kwargs):
28+
"""
29+
Sets up an inquiry request. The optional keyword args
30+
detailed on L{FedexBaseService} apply here as well.
31+
32+
@type config_obj: L{FedexConfig}
33+
@param config_obj: A valid FedexConfig object
34+
@param postal_code: a valid postal code
35+
@param country_code: ISO country code to which the postal code belongs to.
36+
"""
37+
self._config_obj = config_obj
38+
39+
# Holds version info for the VersionId SOAP object.
40+
self._version_info = {'service_id': 'pmis', 'major': '4',
41+
'intermediate': '0', 'minor': '0'}
42+
self.PostalCode = postal_code
43+
self.CountryCode = country_code
44+
45+
46+
# Call the parent FedexBaseService class for basic setup work.
47+
super(PostalCodeInquiryRequest, self).__init__(self._config_obj,
48+
'PackageMovementInformationService_v4.wsdl',
49+
*args, **kwargs)
50+
51+
52+
def _check_response_for_request_errors(self):
53+
"""
54+
Checks the response to see if there were any errors specific to
55+
this WSDL.
56+
"""
57+
if self.response.HighestSeverity == "ERROR":
58+
for notification in self.response.Notifications:
59+
if notification.Severity == "ERROR":
60+
if "Postal Code Not Found" in notification.Message:
61+
raise FedexPostalCodeNotFound(notification.Code,
62+
notification.Message)
63+
64+
elif "Invalid Postal Code Format" in self.response.Notifications:
65+
raise FedexInvalidPostalCodeFormat(notification.Code,
66+
notification.Message)
67+
else:
68+
raise FedexError(notification.Code,
69+
notification.Message)
70+
71+
def _prepare_wsdl_objects(self):
72+
pass
73+
74+
75+
def _assemble_and_send_request(self):
76+
"""
77+
Fires off the Fedex request.
78+
79+
@warning: NEVER CALL THIS METHOD DIRECTLY. CALL send_request(), WHICH RESIDES
80+
ON FedexBaseService AND IS INHERITED.
81+
"""
82+
client = self.client
83+
84+
85+
# We get an exception like this when specifying an IntegratorId:
86+
# suds.TypeNotFound: Type not found: 'IntegratorId'
87+
# Setting it to None does not seem to appease it.
88+
89+
del self.ClientDetail.IntegratorId
90+
91+
# Fire off the query.
92+
response = client.service.postalCodeInquiry(WebAuthenticationDetail=self.WebAuthenticationDetail,
93+
ClientDetail=self.ClientDetail,
94+
TransactionDetail=self.TransactionDetail,
95+
Version=self.VersionId,
96+
PostalCode = self.PostalCode,
97+
CountryCode = self.CountryCode)
98+
99+
return response
100+

fedex/services/rate_service.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""
2+
Rate Service Module
3+
===================
4+
Use the RateService WSDL to request pre-ship rating information and to
5+
determine estimated or courtesy billing quotes. Time in Transit can be
6+
returned with the rates if it is specified in the request.
7+
"""
8+
from datetime import datetime
9+
from .. base_service import FedexBaseService
10+
11+
class FedexRateServiceRequest(FedexBaseService):
12+
"""
13+
This class allows you to get the shipping charges for a particular address.
14+
You will need to populate the data structures in self.RequestedShipment,
15+
then send the request.
16+
"""
17+
def __init__(self, config_obj, *args, **kwargs):
18+
"""
19+
The optional keyword args detailed on L{FedexBaseService}
20+
apply here as well.
21+
22+
@type config_obj: L{FedexConfig}
23+
@param config_obj: A valid FedexConfig object.
24+
"""
25+
self._config_obj = config_obj
26+
27+
# Holds version info for the VersionId SOAP object.
28+
self._version_info = {'service_id': 'crs', 'major': '8',
29+
'intermediate': '0', 'minor': '0'}
30+
31+
self.RequestedShipment = None
32+
"""@ivar: Holds the RequestedShipment WSDL object."""
33+
# Call the parent FedexBaseService class for basic setup work.
34+
super(FedexRateServiceRequest, self).__init__(self._config_obj,
35+
'RateService_v8.wsdl',
36+
*args, **kwargs)
37+
38+
def _prepare_wsdl_objects(self):
39+
"""
40+
This is the data that will be used to create your shipment. Create
41+
the data structure and get it ready for the WSDL request.
42+
"""
43+
# This is the primary data structure for processShipment requests.
44+
self.RequestedShipment = self.client.factory.create('RequestedShipment')
45+
self.RequestedShipment.ShipTimestamp = datetime.now()
46+
47+
TotalWeight = self.client.factory.create('Weight')
48+
# Start at nothing.
49+
TotalWeight.Value = 0.0
50+
# Default to pounds.
51+
TotalWeight.Units = 'LB'
52+
# This is the total weight of the entire shipment. Shipments may
53+
# contain more than one package.
54+
self.RequestedShipment.TotalWeight = TotalWeight
55+
56+
# This is the top level data structure for Shipper information.
57+
ShipperParty = self.client.factory.create('Party')
58+
ShipperParty.Address = self.client.factory.create('Address')
59+
ShipperParty.Contact = self.client.factory.create('Contact')
60+
61+
# Link the ShipperParty to our master data structure.
62+
self.RequestedShipment.Shipper = ShipperParty
63+
64+
# This is the top level data structure for Recipient information.
65+
RecipientParty = self.client.factory.create('Party')
66+
RecipientParty.Contact = self.client.factory.create('Contact')
67+
RecipientParty.Address = self.client.factory.create('Address')
68+
69+
# Link the RecipientParty object to our master data structure.
70+
self.RequestedShipment.Recipient = RecipientParty
71+
72+
Payor = self.client.factory.create('Payor')
73+
# Grab the account number from the FedexConfig object by default.
74+
Payor.AccountNumber = self._config_obj.account_number
75+
# Assume US.
76+
Payor.CountryCode = 'US'
77+
78+
ShippingChargesPayment = self.client.factory.create('Payment')
79+
ShippingChargesPayment.Payor = Payor
80+
81+
self.RequestedShipment.ShippingChargesPayment = ShippingChargesPayment
82+
83+
# ACCOUNT or LIST
84+
self.RequestedShipment.RateRequestTypes = ['ACCOUNT']
85+
86+
# Start with no packages, user must add them.
87+
self.RequestedShipment.PackageCount = 0
88+
self.RequestedShipment.RequestedPackageLineItems = []
89+
90+
# This is good to review if you'd like to see what the data structure
91+
# looks like.
92+
self.logger.debug(self.RequestedShipment)
93+
94+
95+
96+
97+
def _assemble_and_send_request(self):
98+
"""
99+
Fires off the Fedex request.
100+
101+
@warning: NEVER CALL THIS METHOD DIRECTLY. CALL send_request(),
102+
WHICH RESIDES ON FedexBaseService AND IS INHERITED.
103+
"""
104+
# Fire off the query.
105+
response = self.client.service.getRates(WebAuthenticationDetail=self.WebAuthenticationDetail,
106+
ClientDetail=self.ClientDetail,
107+
TransactionDetail=self.TransactionDetail,
108+
Version=self.VersionId,
109+
RequestedShipment=self.RequestedShipment)
110+
return response
111+
112+
def add_package(self, package_item):
113+
"""
114+
Adds a package to the ship request.
115+
116+
@type package_item: WSDL object, type of RequestedPackageLineItem
117+
WSDL object.
118+
@keyword package_item: A RequestedPackageLineItem, created by
119+
calling create_wsdl_object_of_type('RequestedPackageLineItem') on
120+
this ShipmentRequest object. See examples/create_shipment.py for
121+
more details.
122+
"""
123+
self.RequestedShipment.RequestedPackageLineItems.append(package_item)
124+
package_weight = package_item.Weight.Value
125+
self.RequestedShipment.TotalWeight.Value += package_weight
126+
self.RequestedShipment.PackageCount += 1
127+

0 commit comments

Comments
 (0)