From b8b3b1b751d5d57791319f3f8a134a4b30a01568 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Fri, 5 Apr 2019 13:46:15 +0100 Subject: [PATCH 01/41] Inital commit - basics of shipping api --- entities/__init__.py | 0 entities/address.py | 21 ++++++++++++ entities/collection_date.py | 22 ++++++++++++ entities/entity.py | 10 ++++++ entities/parcel.py | 21 ++++++++++++ entities/recipient.py | 18 ++++++++++ entities/sender.py | 22 ++++++++++++ entities/shipment.py | 34 +++++++++++++++++++ library/DBPAPI.py | 67 +++++++++++++++++++++++++++++++++++++ library/__init__.py | 0 10 files changed, 215 insertions(+) create mode 100644 entities/__init__.py create mode 100644 entities/address.py create mode 100644 entities/collection_date.py create mode 100644 entities/entity.py create mode 100644 entities/parcel.py create mode 100644 entities/recipient.py create mode 100644 entities/sender.py create mode 100644 entities/shipment.py create mode 100644 library/DBPAPI.py create mode 100644 library/__init__.py diff --git a/entities/__init__.py b/entities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/entities/address.py b/entities/address.py new file mode 100644 index 0000000..e20702f --- /dev/null +++ b/entities/address.py @@ -0,0 +1,21 @@ +from entity import Entity + + +class Address(Entity): + def __init__(self, client, company_name, street, locality, town_city, county, postal_code, country_code): + + Entity.__init__(self, client) + + self.type_name = 'ns1:AddressType' + self.suds_object = self.client.factory.create(self.type_name) + + self.suds_object.CompanyName = company_name + self.suds_object.Street = street + self.suds_object.Locality = locality + self.suds_object.TownCity = town_city + self.suds_object.County = county + self.suds_object.PostalCode = postal_code + self.suds_object.CountryCode = country_code + + def get_soap_object(self): + return self.suds_object diff --git a/entities/collection_date.py b/entities/collection_date.py new file mode 100644 index 0000000..ae60fae --- /dev/null +++ b/entities/collection_date.py @@ -0,0 +1,22 @@ +from entity import Entity + + +class Sender(Entity): + # todo: handle the exclusivity of senderaddress or senderaddressid + def __init__(self, client, name=None, telephone=None, email=None, address=None, address_id=None): + + Entity.__init__(self, client) + + self.type_name = 'ns1:SenderAddressType' + self.suds_object = self.client.factory.create(self.type_name) + + self.suds_object.SenderName = name + self.suds_object.SenderTelephone = telephone + self.suds_object.SenderEmail = email + if address_id: + self.suds_object.SenderAddressID = address_id + else: + self.suds_object.SenderAddress = address.get_soap_object() + + def get_soap_object(self): + return self.suds_object diff --git a/entities/entity.py b/entities/entity.py new file mode 100644 index 0000000..c101f55 --- /dev/null +++ b/entities/entity.py @@ -0,0 +1,10 @@ +import xml.etree.ElementTree as ET + + +class Entity(object): + + def __init__(self, client): + self.soap_map = None + self.type_name = None + self.client = client + diff --git a/entities/parcel.py b/entities/parcel.py new file mode 100644 index 0000000..85e345c --- /dev/null +++ b/entities/parcel.py @@ -0,0 +1,21 @@ +from entity import Entity + + +class Parcel(Entity): + def __init__(self, client, weight=None, length=None, width=None, height=None, contents=None, value=None, tracking_number=None): + + Entity.__init__(self, client) + + self.type_name = 'ns1:ParcelType' + self.suds_object = self.client.factory.create(self.type_name) + + self.suds_object.Weight = weight + self.suds_object.Length = length + self.suds_object.Width = width + self.suds_object.Height = height + self.suds_object.Contents = contents + self.suds_object.Value = value + self.suds_object.TrackingNumber = tracking_number + + def get_soap_object(self): + return self.suds_object diff --git a/entities/recipient.py b/entities/recipient.py new file mode 100644 index 0000000..e5e1b3b --- /dev/null +++ b/entities/recipient.py @@ -0,0 +1,18 @@ +from entity import Entity + + +class Recipient(Entity): + def __init__(self, client, name=None, telephone=None, email=None, address=None): + + Entity.__init__(self, client) + + self.type_name = 'ns1:RecipientAddressType' + self.suds_object = self.client.factory.create(self.type_name) + + self.suds_object.RecipientName = name + self.suds_object.RecipientTelephone = telephone + self.suds_object.RecipientEmail = email + self.suds_object.RecipientAddress = address.get_soap_object() + + def get_soap_object(self): + return self.suds_object diff --git a/entities/sender.py b/entities/sender.py new file mode 100644 index 0000000..ae60fae --- /dev/null +++ b/entities/sender.py @@ -0,0 +1,22 @@ +from entity import Entity + + +class Sender(Entity): + # todo: handle the exclusivity of senderaddress or senderaddressid + def __init__(self, client, name=None, telephone=None, email=None, address=None, address_id=None): + + Entity.__init__(self, client) + + self.type_name = 'ns1:SenderAddressType' + self.suds_object = self.client.factory.create(self.type_name) + + self.suds_object.SenderName = name + self.suds_object.SenderTelephone = telephone + self.suds_object.SenderEmail = email + if address_id: + self.suds_object.SenderAddressID = address_id + else: + self.suds_object.SenderAddress = address.get_soap_object() + + def get_soap_object(self): + return self.suds_object diff --git a/entities/shipment.py b/entities/shipment.py new file mode 100644 index 0000000..1b3ef7b --- /dev/null +++ b/entities/shipment.py @@ -0,0 +1,34 @@ +from entity import Entity + + +class Shipment(Entity): + def __init__(self, client, service_id=None, parcels=None, client_reference=None, collection_date=None, sender_address=None, recipient_address=None, follow_shipment=None): + + Entity.__init__(self, client) + + self.type_name = 'ns1:ShipmentRequestType' + self.suds_object = self.client.factory.create(self.type_name) + self.suds_object.ServiceID = service_id + + parcel_array = client.factory.create('ns1:ArrayOfParcelType') + parcel_list = [] + for item in parcels: + parcel_list.append(item.get_soap_object()) + parcel_array.item = parcel_list + parcel_array._arrayType = "SOMETHING" + + self.suds_object.Parcels = parcel_array + self.suds_object.ClientReference = client_reference + self.suds_object.CollectionDate = collection_date + self.suds_object.SenderAddress = sender_address.get_soap_object() + self.suds_object.RecipientAddress = recipient_address.get_soap_object() + self.suds_object.FollowShipment = follow_shipment + + + def get_soap_object(self): + return self.suds_object + + + + + diff --git a/library/DBPAPI.py b/library/DBPAPI.py new file mode 100644 index 0000000..36e6d52 --- /dev/null +++ b/library/DBPAPI.py @@ -0,0 +1,67 @@ +from suds.client import Client +from suds.transport.http import HttpAuthenticated +from suds.plugin import MessagePlugin +from suds import WebFault +import suds +from entities import parcel, address, recipient, sender, shipment + +class dbpapi(object): + + def __init__(self): + # todo: set differently + url = 'http://api.despatchbay.com/soap/%s/%s?wsdl' + account_url = url % ('v14', 'account') + shipping_url = url % ('v14', 'shipping') + + # todo: don't hardcode auth + auth = ['user', 'pass'] + t1 = HttpAuthenticated(username=auth[0], password=auth[1]) + t2 = HttpAuthenticated(username=auth[0], password=auth[1]) + t3 = HttpAuthenticated(username=auth[0], password=auth[1]) + t4 = HttpAuthenticated(username=auth[0], password=auth[1]) + self.accounts_client = Client(account_url, transport=t1) + self.addressing_client = Client(shipping_url, transport=t2) + self.shipping_client = Client(shipping_url, transport=t3) + self.shipping_client. + self.tracking_client = Client(shipping_url, transport=t4) + + def parcel(self, **kwargs): + """ + Creates a dbp parcel entity + """ + return parcel.Parcel(self.shipping_client, **kwargs) + + def address(self, **kwargs): + """ + Creates a dbp address entity + """ + return address.Address(self.shipping_client, **kwargs) + + def recipient(self, **kwargs): + """ + Creates a dbp recipient address entity + """ + return recipient.Recipient(self.shipping_client, **kwargs) + + def sender(self, **kwargs): + """ + Creates a dbp sender address entity + """ + return sender.Sender(self.shipping_client, **kwargs) + + def shipment(self, **kwargs): + """ + Creates a dbp shipment entity + """ + return shipment.Shipment(self.shipping_client, **kwargs) + + def get_available_services(self, shipment_request): + try: + return self.shipping_client.service.GetAvailableServices(shipment_request) + except WebFault as e: + print("error!") + print(e) + print(self.shipping_client.last_sent()) + + def add_shipment(self, shipment_request): + return self.shipping_client.service.AddShipment(shipment_request) diff --git a/library/__init__.py b/library/__init__.py new file mode 100644 index 0000000..e69de29 From dc4ddafb7eaeef757f458bf162e533b92fa3b0c4 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Fri, 5 Apr 2019 17:43:25 +0100 Subject: [PATCH 02/41] Basic all calls for shipping API --- entities/shipment.py | 5 +++-- library/DBPAPI.py | 51 +++++++++++++++++++++++++++++++------------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/entities/shipment.py b/entities/shipment.py index 1b3ef7b..c025e3a 100644 --- a/entities/shipment.py +++ b/entities/shipment.py @@ -15,16 +15,17 @@ def __init__(self, client, service_id=None, parcels=None, client_reference=None, for item in parcels: parcel_list.append(item.get_soap_object()) parcel_array.item = parcel_list + #todo: "SOMETHING" more appropriate parcel_array._arrayType = "SOMETHING" - self.suds_object.Parcels = parcel_array self.suds_object.ClientReference = client_reference + collection_date = self.client.factory.create('CollectionDateType') + collection_date.CollectionDate = '2019-04-05' self.suds_object.CollectionDate = collection_date self.suds_object.SenderAddress = sender_address.get_soap_object() self.suds_object.RecipientAddress = recipient_address.get_soap_object() self.suds_object.FollowShipment = follow_shipment - def get_soap_object(self): return self.suds_object diff --git a/library/DBPAPI.py b/library/DBPAPI.py index 36e6d52..b1c6ded 100644 --- a/library/DBPAPI.py +++ b/library/DBPAPI.py @@ -1,30 +1,27 @@ from suds.client import Client from suds.transport.http import HttpAuthenticated -from suds.plugin import MessagePlugin -from suds import WebFault -import suds +from suds import WebFault, TypeNotFound from entities import parcel, address, recipient, sender, shipment -class dbpapi(object): - def __init__(self): +class DespatchBayAPI(object): + + def __init__(self, apiuser, apikey): # todo: set differently - url = 'http://api.despatchbay.com/soap/%s/%s?wsdl' + url = 'http://api.despatchbay.st/soap/%s/%s?wsdl' account_url = url % ('v14', 'account') - shipping_url = url % ('v14', 'shipping') - - # todo: don't hardcode auth - auth = ['user', 'pass'] - t1 = HttpAuthenticated(username=auth[0], password=auth[1]) - t2 = HttpAuthenticated(username=auth[0], password=auth[1]) - t3 = HttpAuthenticated(username=auth[0], password=auth[1]) - t4 = HttpAuthenticated(username=auth[0], password=auth[1]) + shipping_url = url % ('v15', 'shipping') + t1 = HttpAuthenticated(username=apiuser, password=apikey) + t2 = HttpAuthenticated(username=apiuser, password=apikey) + t3 = HttpAuthenticated(username=apiuser, password=apikey) + t4 = HttpAuthenticated(username=apiuser, password=apikey) self.accounts_client = Client(account_url, transport=t1) self.addressing_client = Client(shipping_url, transport=t2) self.shipping_client = Client(shipping_url, transport=t3) - self.shipping_client. self.tracking_client = Client(shipping_url, transport=t4) + # Shipping entities + def parcel(self, **kwargs): """ Creates a dbp parcel entity @@ -55,6 +52,8 @@ def shipment(self, **kwargs): """ return shipment.Shipment(self.shipping_client, **kwargs) + # Shipping services + def get_available_services(self, shipment_request): try: return self.shipping_client.service.GetAvailableServices(shipment_request) @@ -63,5 +62,27 @@ def get_available_services(self, shipment_request): print(e) print(self.shipping_client.last_sent()) + def get_collection(self, collection_id): + return self.shipping_client.service.GetCollection(collection_id) + + def get_collections(self): + return self.shipping_client.service.GetCollections() + + def get_available_collection_dates(self, sender_address, courier_id): + try: + return self.shipping_client.service.GetAvailableCollectionDates(sender_address, courier_id) + except TypeNotFound as e: + print("last sent: ") + print(self.shipping_client.last_sent()) + + def get_shipment(self, shipment_id): + return self.shipping_client.service.GetShipment(shipment_id) + def add_shipment(self, shipment_request): return self.shipping_client.service.AddShipment(shipment_request) + + def book_shipments(self, shipment_ids): + return self.shipping_client.service.BookShipments(shipment_ids) + + def cancel_shipment(self, shipment_id): + return self.shipping_client.service.CancelShipment(shipment_id) From 7db85c06c9976555e900ab8d73e1510dcb4b72ee Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Mon, 8 Apr 2019 14:20:07 +0100 Subject: [PATCH 03/41] Shipping entities easier to update, implemented account functions --- entities/address.py | 37 ++++++++++++----------- entities/collection_date.py | 22 -------------- entities/entity.py | 10 ------- entities/parcel.py | 37 ++++++++++++----------- entities/recipient.py | 28 ++++++++--------- entities/sender.py | 34 ++++++++++----------- entities/shipment.py | 60 +++++++++++++++++-------------------- library/DBPAPI.py | 24 ++++++++++----- 8 files changed, 111 insertions(+), 141 deletions(-) delete mode 100644 entities/collection_date.py delete mode 100644 entities/entity.py diff --git a/entities/address.py b/entities/address.py index e20702f..7c7094e 100644 --- a/entities/address.py +++ b/entities/address.py @@ -1,21 +1,22 @@ -from entity import Entity - - -class Address(Entity): +class Address(object): def __init__(self, client, company_name, street, locality, town_city, county, postal_code, country_code): - - Entity.__init__(self, client) - + self.client = client self.type_name = 'ns1:AddressType' - self.suds_object = self.client.factory.create(self.type_name) - - self.suds_object.CompanyName = company_name - self.suds_object.Street = street - self.suds_object.Locality = locality - self.suds_object.TownCity = town_city - self.suds_object.County = county - self.suds_object.PostalCode = postal_code - self.suds_object.CountryCode = country_code + self.company_name = company_name + self.street = street + self.locality = locality + self.town_city = town_city + self.county = county + self.postal_code = postal_code + self.country_code = country_code - def get_soap_object(self): - return self.suds_object + def to_soap_object(self): + suds_object = self.client.factory.create(self.type_name) + suds_object.CompanyName = self.company_name + suds_object.Street = self.street + suds_object.Locality = self.locality + suds_object.TownCity = self.town_city + suds_object.County = self.county + suds_object.PostalCode = self.postal_code + suds_object.CountryCode = self.country_code + return suds_object diff --git a/entities/collection_date.py b/entities/collection_date.py deleted file mode 100644 index ae60fae..0000000 --- a/entities/collection_date.py +++ /dev/null @@ -1,22 +0,0 @@ -from entity import Entity - - -class Sender(Entity): - # todo: handle the exclusivity of senderaddress or senderaddressid - def __init__(self, client, name=None, telephone=None, email=None, address=None, address_id=None): - - Entity.__init__(self, client) - - self.type_name = 'ns1:SenderAddressType' - self.suds_object = self.client.factory.create(self.type_name) - - self.suds_object.SenderName = name - self.suds_object.SenderTelephone = telephone - self.suds_object.SenderEmail = email - if address_id: - self.suds_object.SenderAddressID = address_id - else: - self.suds_object.SenderAddress = address.get_soap_object() - - def get_soap_object(self): - return self.suds_object diff --git a/entities/entity.py b/entities/entity.py deleted file mode 100644 index c101f55..0000000 --- a/entities/entity.py +++ /dev/null @@ -1,10 +0,0 @@ -import xml.etree.ElementTree as ET - - -class Entity(object): - - def __init__(self, client): - self.soap_map = None - self.type_name = None - self.client = client - diff --git a/entities/parcel.py b/entities/parcel.py index 85e345c..abb5950 100644 --- a/entities/parcel.py +++ b/entities/parcel.py @@ -1,21 +1,22 @@ -from entity import Entity - - -class Parcel(Entity): +class Parcel(object): def __init__(self, client, weight=None, length=None, width=None, height=None, contents=None, value=None, tracking_number=None): - - Entity.__init__(self, client) - + self.client = client self.type_name = 'ns1:ParcelType' - self.suds_object = self.client.factory.create(self.type_name) - - self.suds_object.Weight = weight - self.suds_object.Length = length - self.suds_object.Width = width - self.suds_object.Height = height - self.suds_object.Contents = contents - self.suds_object.Value = value - self.suds_object.TrackingNumber = tracking_number + self.weight = weight + self.length = length + self.width = width + self.height = height + self.contents = contents + self.value = value + self.tracking_number = tracking_number - def get_soap_object(self): - return self.suds_object + def to_soap_object(self): + suds_object = self.client.factory.create(self.type_name) + suds_object.Weight = self.weight + suds_object.Length = self.length + suds_object.Width = self.width + suds_object.Height = self.height + suds_object.Contents = self.contents + suds_object.Value = self.value + suds_object.TrackingNumber = self.tracking_number + return suds_object diff --git a/entities/recipient.py b/entities/recipient.py index e5e1b3b..f4ed78c 100644 --- a/entities/recipient.py +++ b/entities/recipient.py @@ -1,18 +1,16 @@ -from entity import Entity - - -class Recipient(Entity): +class Recipient(object): def __init__(self, client, name=None, telephone=None, email=None, address=None): - - Entity.__init__(self, client) - + self.client = client self.type_name = 'ns1:RecipientAddressType' - self.suds_object = self.client.factory.create(self.type_name) - - self.suds_object.RecipientName = name - self.suds_object.RecipientTelephone = telephone - self.suds_object.RecipientEmail = email - self.suds_object.RecipientAddress = address.get_soap_object() + self.name = name + self.telephone = telephone + self.email = email + self.address = address - def get_soap_object(self): - return self.suds_object + def to_soap_object(self): + suds_object = self.client.factory.create(self.type_name) + suds_object.RecipientName = self.name + suds_object.RecipientTelephone = self.telephone + suds_object.RecipientEmail = self.email + suds_object.RecipientAddress = self.address.to_soap_object() + return suds_object diff --git a/entities/sender.py b/entities/sender.py index ae60fae..57a4b45 100644 --- a/entities/sender.py +++ b/entities/sender.py @@ -1,22 +1,20 @@ -from entity import Entity - - -class Sender(Entity): - # todo: handle the exclusivity of senderaddress or senderaddressid +class Sender(object): def __init__(self, client, name=None, telephone=None, email=None, address=None, address_id=None): - - Entity.__init__(self, client) - + self.client = client self.type_name = 'ns1:SenderAddressType' - self.suds_object = self.client.factory.create(self.type_name) + self.name = name + self.telephone = telephone + self.email = email + self.address_id = address_id + self.address = address - self.suds_object.SenderName = name - self.suds_object.SenderTelephone = telephone - self.suds_object.SenderEmail = email - if address_id: - self.suds_object.SenderAddressID = address_id + def to_soap_object(self): + suds_object = self.client.factory.create(self.type_name) + suds_object.SenderName = self.name + suds_object.SenderTelephone = self.telephone + suds_object.SenderEmail = self.email + if self.address_id: + suds_object.SenderAddressID = self.address_id else: - self.suds_object.SenderAddress = address.get_soap_object() - - def get_soap_object(self): - return self.suds_object + suds_object.SenderAddress = self.address.to_soap_object() + return suds_object diff --git a/entities/shipment.py b/entities/shipment.py index c025e3a..c1aab5c 100644 --- a/entities/shipment.py +++ b/entities/shipment.py @@ -1,35 +1,31 @@ -from entity import Entity - - -class Shipment(Entity): +class Shipment(object): def __init__(self, client, service_id=None, parcels=None, client_reference=None, collection_date=None, sender_address=None, recipient_address=None, follow_shipment=None): - - Entity.__init__(self, client) - + self.client = client self.type_name = 'ns1:ShipmentRequestType' - self.suds_object = self.client.factory.create(self.type_name) - self.suds_object.ServiceID = service_id - - parcel_array = client.factory.create('ns1:ArrayOfParcelType') - parcel_list = [] - for item in parcels: - parcel_list.append(item.get_soap_object()) - parcel_array.item = parcel_list - #todo: "SOMETHING" more appropriate - parcel_array._arrayType = "SOMETHING" - self.suds_object.Parcels = parcel_array - self.suds_object.ClientReference = client_reference + self.service_id = service_id + self.parcels = parcels + self.client_reference = client_reference + self.collection_date = collection_date + self.sender_address = sender_address + self.recipient_address = recipient_address + self.follow_shipment = follow_shipment + + def to_soap_object(self): + suds_object = self.client.factory.create(self.type_name) + parcel_array = self.client.factory.create('ns1:ArrayOfParcelType') + soap_parcel_list = [] + for item in self.parcels: + soap_parcel_list.append(item.to_soap_object()) + print(soap_parcel_list) + parcel_array.item = soap_parcel_list + parcel_array._arrayType = "urn:ParcelType[]" collection_date = self.client.factory.create('CollectionDateType') - collection_date.CollectionDate = '2019-04-05' - self.suds_object.CollectionDate = collection_date - self.suds_object.SenderAddress = sender_address.get_soap_object() - self.suds_object.RecipientAddress = recipient_address.get_soap_object() - self.suds_object.FollowShipment = follow_shipment - - def get_soap_object(self): - return self.suds_object - - - - - + collection_date.CollectionDate = self.collection_date + suds_object.ServiceID = self.service_id + suds_object.Parcels = parcel_array + suds_object.ClientReference = self.client_reference + suds_object.CollectionDate = collection_date + suds_object.SenderAddress = self.sender_address.to_soap_object() + suds_object.RecipientAddress = self.recipient_address.to_soap_object() + suds_object.FollowShipment = self.follow_shipment + return suds_object diff --git a/library/DBPAPI.py b/library/DBPAPI.py index b1c6ded..537029b 100644 --- a/library/DBPAPI.py +++ b/library/DBPAPI.py @@ -52,15 +52,22 @@ def shipment(self, **kwargs): """ return shipment.Shipment(self.shipping_client, **kwargs) + # Account Services + + def get_account(self): + return self.accounts_client.service.GetAccount() + + def get_account_balance(self): + return self.accounts_client.service.GetAccountBalance() + + def get_sender_addresses(self): + return self.accounts_client.service.GetSenderAddresses() + # Shipping services def get_available_services(self, shipment_request): - try: - return self.shipping_client.service.GetAvailableServices(shipment_request) - except WebFault as e: - print("error!") - print(e) - print(self.shipping_client.last_sent()) + return self.shipping_client.service.GetAvailableServices( + shipment_request.to_soap_object()) def get_collection(self, collection_id): return self.shipping_client.service.GetCollection(collection_id) @@ -70,7 +77,8 @@ def get_collections(self): def get_available_collection_dates(self, sender_address, courier_id): try: - return self.shipping_client.service.GetAvailableCollectionDates(sender_address, courier_id) + return self.shipping_client.service.GetAvailableCollectionDates( + sender_address.to_soap_object(), courier_id) except TypeNotFound as e: print("last sent: ") print(self.shipping_client.last_sent()) @@ -79,7 +87,7 @@ def get_shipment(self, shipment_id): return self.shipping_client.service.GetShipment(shipment_id) def add_shipment(self, shipment_request): - return self.shipping_client.service.AddShipment(shipment_request) + return self.shipping_client.service.AddShipment(shipment_request.to_soap_object()) def book_shipments(self, shipment_ids): return self.shipping_client.service.BookShipments(shipment_ids) From e4c3039b9f4cca3557b2257f4d38d2c1fb60d410 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Mon, 8 Apr 2019 14:40:53 +0100 Subject: [PATCH 04/41] Addressing api --- library/DBPAPI.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/library/DBPAPI.py b/library/DBPAPI.py index 537029b..a47ab91 100644 --- a/library/DBPAPI.py +++ b/library/DBPAPI.py @@ -9,17 +9,20 @@ class DespatchBayAPI(object): def __init__(self, apiuser, apikey): # todo: set differently url = 'http://api.despatchbay.st/soap/%s/%s?wsdl' - account_url = url % ('v14', 'account') + account_url = url % ('v15', 'account') shipping_url = url % ('v15', 'shipping') + addressing_url = url % ('v15', 'addressing') t1 = HttpAuthenticated(username=apiuser, password=apikey) t2 = HttpAuthenticated(username=apiuser, password=apikey) t3 = HttpAuthenticated(username=apiuser, password=apikey) t4 = HttpAuthenticated(username=apiuser, password=apikey) self.accounts_client = Client(account_url, transport=t1) - self.addressing_client = Client(shipping_url, transport=t2) + self.addressing_client = Client(addressing_url, transport=t2) self.shipping_client = Client(shipping_url, transport=t3) self.tracking_client = Client(shipping_url, transport=t4) + print(addressing_url) + # Shipping entities def parcel(self, **kwargs): @@ -63,6 +66,17 @@ def get_account_balance(self): def get_sender_addresses(self): return self.accounts_client.service.GetSenderAddresses() + # Addressing Services + + def find_address(self, postcode, property): + return self.addressing_client.service.FindAddress(postcode, property) + + def get_address_by_key(self, key): + return self.addressing_client.service.GetAddressByKey(key) + + def get_address_keys_by_postcode(self, postcode): + return self.addressing_client.service.GetAddressKeysByPostcode(postcode) + # Shipping services def get_available_services(self, shipment_request): From 39e516878108c2d92bdd2c8088863c3abdfd612b Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Mon, 8 Apr 2019 14:48:18 +0100 Subject: [PATCH 05/41] Tracking API --- library/DBPAPI.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/library/DBPAPI.py b/library/DBPAPI.py index a47ab91..3716f67 100644 --- a/library/DBPAPI.py +++ b/library/DBPAPI.py @@ -12,6 +12,7 @@ def __init__(self, apiuser, apikey): account_url = url % ('v15', 'account') shipping_url = url % ('v15', 'shipping') addressing_url = url % ('v15', 'addressing') + tracking_url = url % ('v15', 'tracking') t1 = HttpAuthenticated(username=apiuser, password=apikey) t2 = HttpAuthenticated(username=apiuser, password=apikey) t3 = HttpAuthenticated(username=apiuser, password=apikey) @@ -19,8 +20,7 @@ def __init__(self, apiuser, apikey): self.accounts_client = Client(account_url, transport=t1) self.addressing_client = Client(addressing_url, transport=t2) self.shipping_client = Client(shipping_url, transport=t3) - self.tracking_client = Client(shipping_url, transport=t4) - + self.tracking_client = Client(tracking_url, transport=t4) print(addressing_url) # Shipping entities @@ -108,3 +108,8 @@ def book_shipments(self, shipment_ids): def cancel_shipment(self, shipment_id): return self.shipping_client.service.CancelShipment(shipment_id) + + # Tracking services + + def get_tracking(self, tracking_number): + return self.tracking_client.service.GetTracking(tracking_number) From 5fff810b53367f88b54f4d1f9b28b9c96bec92e4 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Tue, 9 Apr 2019 15:58:28 +0100 Subject: [PATCH 06/41] Shipment date type --- entities/shipment.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/entities/shipment.py b/entities/shipment.py index c1aab5c..f204f24 100644 --- a/entities/shipment.py +++ b/entities/shipment.py @@ -1,3 +1,6 @@ +import suds.sudsobject + + class Shipment(object): def __init__(self, client, service_id=None, parcels=None, client_reference=None, collection_date=None, sender_address=None, recipient_address=None, follow_shipment=None): self.client = client @@ -19,8 +22,11 @@ def to_soap_object(self): print(soap_parcel_list) parcel_array.item = soap_parcel_list parcel_array._arrayType = "urn:ParcelType[]" - collection_date = self.client.factory.create('CollectionDateType') - collection_date.CollectionDate = self.collection_date + if isinstance(self.collection_date, str): + collection_date = self.client.factory.create('CollectionDateType') + collection_date.CollectionDate = self.collection_date + else: + collection_date = self.collection_date suds_object.ServiceID = self.service_id suds_object.Parcels = parcel_array suds_object.ClientReference = self.client_reference From f5c4103d8357d68cee87dc76a9ea76c1efc33266 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Tue, 9 Apr 2019 15:58:42 +0100 Subject: [PATCH 07/41] Labels api --- library/DBPAPI.py | 65 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/library/DBPAPI.py b/library/DBPAPI.py index 3716f67..fe474f2 100644 --- a/library/DBPAPI.py +++ b/library/DBPAPI.py @@ -1,6 +1,11 @@ +from urllib.parse import urlencode +import base64 + +import requests from suds.client import Client from suds.transport.http import HttpAuthenticated from suds import WebFault, TypeNotFound + from entities import parcel, address, recipient, sender, shipment @@ -8,11 +13,13 @@ class DespatchBayAPI(object): def __init__(self, apiuser, apikey): # todo: set differently - url = 'http://api.despatchbay.st/soap/%s/%s?wsdl' - account_url = url % ('v15', 'account') - shipping_url = url % ('v15', 'shipping') - addressing_url = url % ('v15', 'addressing') - tracking_url = url % ('v15', 'tracking') + url = 'http://api.despatchbay.st' + soap_path = '/soap/%s/%s?wsdl' + documents_path = '/documents/v1/' + account_url = url + soap_path % ('v15', 'account') + shipping_url = url + soap_path % ('v15', 'shipping') + addressing_url = url + soap_path % ('v15', 'addressing') + tracking_url = url + soap_path % ('v15', 'tracking') t1 = HttpAuthenticated(username=apiuser, password=apikey) t2 = HttpAuthenticated(username=apiuser, password=apikey) t3 = HttpAuthenticated(username=apiuser, password=apikey) @@ -21,6 +28,8 @@ def __init__(self, apiuser, apikey): self.addressing_client = Client(addressing_url, transport=t2) self.shipping_client = Client(shipping_url, transport=t3) self.tracking_client = Client(tracking_url, transport=t4) + self.labels_url = url + documents_path + 'labels' + self.manifest_url = url + documents_path + 'manifest' print(addressing_url) # Shipping entities @@ -113,3 +122,49 @@ def cancel_shipment(self, shipment_id): def get_tracking(self, tracking_number): return self.tracking_client.service.GetTracking(tracking_number) + + # Labels services + + def get_shipment_labels(self, ship_collect_ids, download_path, layout=None, + label_format=None, label_dpi=None): + if isinstance(ship_collect_ids, list): + shipment_string = ','.join(ship_collect_ids) + else: + shipment_string = ship_collect_ids + query_dict = {} + if layout: + query_dict['layout'] = layout + if label_format: + query_dict['format'] = label_format + if label_format == 'png_base64' and label_dpi: + query_dict['dpi'] = label_dpi + label_request_url = '{}/{}'.format(self.labels_url, + shipment_string) + if query_dict: + query_string = urlencode(query_dict) + label_request_url = label_request_url + '?' + query_string + r = requests.get(label_request_url) + if label_format == 'png_base64' or label_format == 'pdf_base64': + label_data = base64.b64decode(r.content) + else: + label_data = r.content + with open(download_path, 'wb') as label_file: + label_file.write(label_data) + + def get_manifest(self, collection_id, download_path, manifest_format=None): + query_dict = {} + if manifest_format: + query_dict['format'] = manifest_format + manifest_request_url = '{}/{}'.format(self.manifest_url, + collection_id) + if query_dict: + query_string = urlencode(query_dict) + manifest_request_url = manifest_request_url + '?' + query_string + r = requests.get(manifest_request_url) + if manifest_format == 'base64': + manifest_data = base64.b64decode(r.content) + else: + manifest_data = r.content + with open(download_path, 'wb') as manifest_file: + manifest_file.write(manifest_data) + From d081e349c6e435de21a7ed955a810982b7faf53b Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Tue, 9 Apr 2019 16:07:29 +0100 Subject: [PATCH 08/41] Import tidy --- library/DBPAPI.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/library/DBPAPI.py b/library/DBPAPI.py index fe474f2..f7f7693 100644 --- a/library/DBPAPI.py +++ b/library/DBPAPI.py @@ -4,7 +4,6 @@ import requests from suds.client import Client from suds.transport.http import HttpAuthenticated -from suds import WebFault, TypeNotFound from entities import parcel, address, recipient, sender, shipment @@ -99,12 +98,8 @@ def get_collections(self): return self.shipping_client.service.GetCollections() def get_available_collection_dates(self, sender_address, courier_id): - try: - return self.shipping_client.service.GetAvailableCollectionDates( - sender_address.to_soap_object(), courier_id) - except TypeNotFound as e: - print("last sent: ") - print(self.shipping_client.last_sent()) + return self.shipping_client.service.GetAvailableCollectionDates( + sender_address.to_soap_object(), courier_id) def get_shipment(self, shipment_id): return self.shipping_client.service.GetShipment(shipment_id) @@ -125,8 +120,8 @@ def get_tracking(self, tracking_number): # Labels services - def get_shipment_labels(self, ship_collect_ids, download_path, layout=None, - label_format=None, label_dpi=None): + def download_shipment_labels(self, ship_collect_ids, download_path, layout=None, + label_format=None, label_dpi=None): if isinstance(ship_collect_ids, list): shipment_string = ','.join(ship_collect_ids) else: @@ -151,7 +146,7 @@ def get_shipment_labels(self, ship_collect_ids, download_path, layout=None, with open(download_path, 'wb') as label_file: label_file.write(label_data) - def get_manifest(self, collection_id, download_path, manifest_format=None): + def download_manifest(self, collection_id, download_path, manifest_format=None): query_dict = {} if manifest_format: query_dict['format'] = manifest_format From f91950f77baa357477732d628e4a6efc64feef59 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Wed, 10 Apr 2019 17:24:41 +0100 Subject: [PATCH 09/41] Return sdk objects from accounts and addressing calls --- entities/account.py | 35 +++++++++++++++++++++++++++ entities/account_balance.py | 26 ++++++++++++++++++++ entities/address.py | 13 ++++++++++ entities/address_key.py | 20 ++++++++++++++++ entities/sender.py | 16 +++++++++++++ library/DBPAPI.py | 48 +++++++++++++++++++++++++++++++------ 6 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 entities/account.py create mode 100644 entities/account_balance.py create mode 100644 entities/address_key.py diff --git a/entities/account.py b/entities/account.py new file mode 100644 index 0000000..a97d5e5 --- /dev/null +++ b/entities/account.py @@ -0,0 +1,35 @@ +import account_balance + + +class Account(object): + arg_map = { + 'AccountID': 'id', + 'AccountName': 'name', + 'AccountBalance': 'balance' + } + + def __init__(self, client, id=None, name=None, balance=None): + self.client = client + self.type_name = 'ns1:AccountType' + self.id = id + self.name = name + self.balance = balance + + @classmethod + def from_dict(cls, client, **kwargs): + return cls( + client=client, + id=kwargs.get('AccountID', None), + name=kwargs.get('AccountName', None), + balance=account_balance.AccountBalance.from_dict( + client, + **client.accounts_client.dict(kwargs.get('AccountBalance', None)) + ) + ) + + def to_soap_object(self): + suds_object = self.client.factory.create(self.type_name) + suds_object.AccountID = self.id + suds_object.AccountName = self.name + suds_object.AccountBalance = self.balance + return suds_object diff --git a/entities/account_balance.py b/entities/account_balance.py new file mode 100644 index 0000000..4b9a1d8 --- /dev/null +++ b/entities/account_balance.py @@ -0,0 +1,26 @@ +class AccountBalance(object): + arg_map = { + 'Balance': 'balance', + 'AvailableBalance': 'available' + } + + def __init__(self, client, balance=None, available=None): + self.client = client + self.type_name = 'ns1:BalanceType' + self.balance = balance + self.available = available + + @classmethod + def from_dict(cls, client, **kwargs): + print(kwargs) + return cls( + client=client, + balance=kwargs.get('Balance', None), + available=kwargs.get('AvailableBalance', None) + ) + + def to_soap_object(self): + suds_object = self.client.factory.create(self.type_name) + suds_object.Balance= self.balance + suds_object.AvailableBalance = self.available + return suds_object diff --git a/entities/address.py b/entities/address.py index 7c7094e..dcf331a 100644 --- a/entities/address.py +++ b/entities/address.py @@ -10,6 +10,19 @@ def __init__(self, client, company_name, street, locality, town_city, county, po self.postal_code = postal_code self.country_code = country_code + @classmethod + def from_dict(cls, client, **kwargs): + return cls( + client=client, + company_name=kwargs.get('CompanyName', None), + street=kwargs.get('Street', None), + locality=kwargs.get('Locality', None), + town_city=kwargs.get('TownCity', None), + county=kwargs.get('County', None), + postal_code=kwargs.get('PostalCode', None), + country_code=kwargs.get('CountryCode', None) + ) + def to_soap_object(self): suds_object = self.client.factory.create(self.type_name) suds_object.CompanyName = self.company_name diff --git a/entities/address_key.py b/entities/address_key.py new file mode 100644 index 0000000..b224bed --- /dev/null +++ b/entities/address_key.py @@ -0,0 +1,20 @@ +class AddressKey(object): + def __init__(self, client, key, address): + self.client = client + self.type_name = 'ns1:AddressKeyType' + self.key = key + self.address = address + + @classmethod + def from_dict(cls, client, **kwargs): + return cls( + client=client, + key=kwargs.get('Key', None), + address=kwargs.get('Address', None) + ) + + def to_soap_object(self): + suds_object = self.client.factory.create(self.type_name) + suds_object.key = self.key + suds_object.address = self.address + return suds_object diff --git a/entities/sender.py b/entities/sender.py index 57a4b45..4f01e30 100644 --- a/entities/sender.py +++ b/entities/sender.py @@ -1,3 +1,5 @@ +import address + class Sender(object): def __init__(self, client, name=None, telephone=None, email=None, address=None, address_id=None): self.client = client @@ -8,6 +10,20 @@ def __init__(self, client, name=None, telephone=None, email=None, address=None, self.address_id = address_id self.address = address + @classmethod + def from_dict(cls, client, **kwargs): + return cls( + client=client, + name=kwargs.get('SenderName', None), + telephone=kwargs.get('SenderTelephone', None), + email=kwargs.get('SenderEmail', None), + address_id=kwargs.get('SenderAddressID'), + address=address.Address.from_dict( + client, + **client.accounts_client.dict(kwargs.get('SenderAddress', None)) + ) + ) + def to_soap_object(self): suds_object = self.client.factory.create(self.type_name) suds_object.SenderName = self.name diff --git a/library/DBPAPI.py b/library/DBPAPI.py index f7f7693..61d8204 100644 --- a/library/DBPAPI.py +++ b/library/DBPAPI.py @@ -5,7 +5,7 @@ from suds.client import Client from suds.transport.http import HttpAuthenticated -from entities import parcel, address, recipient, sender, shipment +from entities import parcel, address, recipient, sender, shipment, account, account_balance, address_key class DespatchBayAPI(object): @@ -66,24 +66,58 @@ def shipment(self, **kwargs): # Account Services def get_account(self): - return self.accounts_client.service.GetAccount() + account_data = self.accounts_client.service.GetAccount() + account_dict = self.accounts_client.dict(account_data) + account_object = account.Account.from_dict(self.accounts_client, **account_dict) + return account_object def get_account_balance(self): - return self.accounts_client.service.GetAccountBalance() + balance_data = self.accounts_client.service.GetAccountBalance() + balance_dict = self.accounts_client.dict(balance_data) + balance_object = account_balance.AccountBalance.from_dict( + self.accounts_client, + **balance_dict) + return balance_object def get_sender_addresses(self): - return self.accounts_client.service.GetSenderAddresses() + sender_addresses_data = self.accounts_client.service.GetSenderAddresses() + sender_addresses_dict_list = [] + for sender_address in sender_addresses_data: + sender_address_dict = self.accounts_client.dict(sender_address) + sender_addresses_dict_list.append(sender.Sender.from_dict( + self.accounts_client, + **sender_address_dict)) + return sender_addresses_dict_list # Addressing Services def find_address(self, postcode, property): - return self.addressing_client.service.FindAddress(postcode, property) + found_address = self.addressing_client.service.FindAddress(postcode, property) + found_address_dict = self.addressing_client.dict(found_address) + found_address_object = address.Address.from_dict( + self.addressing_client, + **found_address_dict + ) + return found_address_object def get_address_by_key(self, key): - return self.addressing_client.service.GetAddressByKey(key) + found_address = self.addressing_client.service.GetAddressByKey(key) + found_address_dict = self.addressing_client.dict(found_address) + found_address_object = address.Address.from_dict( + self.addressing_client, + **found_address_dict + ) + return found_address_object def get_address_keys_by_postcode(self, postcode): - return self.addressing_client.service.GetAddressKeysByPostcode(postcode) + address_key_return = self.addressing_client.service.GetAddressKeysByPostcode(postcode) + address_keys_dict_list = [] + for soap_address_key in address_key_return: + address_key_dict = self.accounts_client.dict(soap_address_key) + address_keys_dict_list.append(address_key.AddressKey.from_dict( + self.addressing_client, + **address_key_dict)) + return address_keys_dict_list # Shipping services From c5dd1a3c5adbc78d73a320ee182b6d6c8b41d14e Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Wed, 10 Apr 2019 18:24:47 +0100 Subject: [PATCH 10/41] WIP on shipment return objects --- entities/courier.py | 20 ++++++++++++++++++++ entities/parcel.py | 13 +++++++++++++ entities/recipient.py | 16 ++++++++++++++++ entities/sender.py | 1 + entities/service.py | 32 ++++++++++++++++++++++++++++++++ entities/shipment.py | 2 +- library/DBPAPI.py | 21 +++++++++++++-------- 7 files changed, 96 insertions(+), 9 deletions(-) create mode 100644 entities/courier.py create mode 100644 entities/service.py diff --git a/entities/courier.py b/entities/courier.py new file mode 100644 index 0000000..5e45ddb --- /dev/null +++ b/entities/courier.py @@ -0,0 +1,20 @@ +class Courier(object): + def __init__(self,client, id, name): + self.client = client + self.type_name = 'ns1:CourierType' + self.id = id + self.name = name + + @classmethod + def from_dict(cls, client, **kwargs): + return cls( + client=client, + id=kwargs.get('CourierID', None), + name=kwargs.get('CourierName', None) + ) + + def to_soap_object(self): + suds_object = self.client.factory.create(self.type_name) + suds_object.CourierID= self.id + suds_object.CourierName = self.name + return suds_object diff --git a/entities/parcel.py b/entities/parcel.py index abb5950..566eb68 100644 --- a/entities/parcel.py +++ b/entities/parcel.py @@ -10,6 +10,19 @@ def __init__(self, client, weight=None, length=None, width=None, height=None, co self.value = value self.tracking_number = tracking_number + @classmethod + def from_dict(cls, client, **kwargs): + return cls( + client=client, + weight=kwargs.get('Weight', None), + length=kwargs.get('Length', None), + width=kwargs.get('Width', None), + height=kwargs.get('Height', None), + contents=kwargs.get('Contents', None), + value=kwargs.get('Value', None), + tracking_number=kwargs.get('TrackingNumber', None) + ) + def to_soap_object(self): suds_object = self.client.factory.create(self.type_name) suds_object.Weight = self.weight diff --git a/entities/recipient.py b/entities/recipient.py index f4ed78c..155e2ca 100644 --- a/entities/recipient.py +++ b/entities/recipient.py @@ -1,3 +1,6 @@ +import address + + class Recipient(object): def __init__(self, client, name=None, telephone=None, email=None, address=None): self.client = client @@ -7,6 +10,19 @@ def __init__(self, client, name=None, telephone=None, email=None, address=None): self.email = email self.address = address + @classmethod + def from_dict(cls, client, **kwargs): + return cls( + client=client, + name=kwargs.get('RecipientName', None), + telephone=kwargs.get('RecipientTelephone', None), + email=kwargs.get('RecipientEmail', None), + address=address.Address.from_dict( + client, + **client.accounts_client.dict(kwargs.get('RecipientAddress', None)) + ) + ) + def to_soap_object(self): suds_object = self.client.factory.create(self.type_name) suds_object.RecipientName = self.name diff --git a/entities/sender.py b/entities/sender.py index 4f01e30..8b62f59 100644 --- a/entities/sender.py +++ b/entities/sender.py @@ -1,5 +1,6 @@ import address + class Sender(object): def __init__(self, client, name=None, telephone=None, email=None, address=None, address_id=None): self.client = client diff --git a/entities/service.py b/entities/service.py new file mode 100644 index 0000000..faf675f --- /dev/null +++ b/entities/service.py @@ -0,0 +1,32 @@ +import courier + + +class Service(object): + def __init__(self,client, id, service_format, name, cost, courier): + self.client = client + self.type_name = 'ns1:CourierType' + self.id = id + self.format = service_format + self.name = name + self.cost = cost + self.courier = courier + + @classmethod + def from_dict(cls, client, **kwargs): + return cls( + client=client, + id=kwargs.get('ServiceID', None), + service_format=kwargs.get('Format', None), + name=kwargs.get('Name', None), + cost=kwargs.get('Cost', None), + courier=courier.Courier.from_dict( + client, + **client.dict(kwargs.get('Courier', None)) + ) + ) + + def to_soap_object(self): + suds_object = self.client.factory.create(self.type_name) + suds_object.CourierID= self.id + suds_object.CourierName = self.name + return suds_object diff --git a/entities/shipment.py b/entities/shipment.py index f204f24..ab9b2aa 100644 --- a/entities/shipment.py +++ b/entities/shipment.py @@ -1,4 +1,4 @@ -import suds.sudsobject +import recipient, sender, parcel class Shipment(object): diff --git a/library/DBPAPI.py b/library/DBPAPI.py index 61d8204..71a5ebf 100644 --- a/library/DBPAPI.py +++ b/library/DBPAPI.py @@ -5,7 +5,7 @@ from suds.client import Client from suds.transport.http import HttpAuthenticated -from entities import parcel, address, recipient, sender, shipment, account, account_balance, address_key +from entities import parcel, address, recipient, sender, shipment, account, account_balance, address_key, service class DespatchBayAPI(object): @@ -91,8 +91,8 @@ def get_sender_addresses(self): # Addressing Services - def find_address(self, postcode, property): - found_address = self.addressing_client.service.FindAddress(postcode, property) + def find_address(self, postcode, property_string): + found_address = self.addressing_client.service.FindAddress(postcode, property_string) found_address_dict = self.addressing_client.dict(found_address) found_address_object = address.Address.from_dict( self.addressing_client, @@ -122,18 +122,24 @@ def get_address_keys_by_postcode(self, postcode): # Shipping services def get_available_services(self, shipment_request): - return self.shipping_client.service.GetAvailableServices( + available_services_response = self.shipping_client.service.GetAvailableServices( shipment_request.to_soap_object()) + available_service_dict_list = [] + for available_service in available_services_response: + available_service_dict = self.accounts_client.dict(available_service) + available_service_dict_list.append(service.Service.from_dict( + self.shipping_client, + **available_service_dict)) + return available_service_dict_list def get_collection(self, collection_id): return self.shipping_client.service.GetCollection(collection_id) - def get_collections(self): - return self.shipping_client.service.GetCollections() - def get_available_collection_dates(self, sender_address, courier_id): return self.shipping_client.service.GetAvailableCollectionDates( sender_address.to_soap_object(), courier_id) + def get_collections(self): + return self.shipping_client.service.GetCollections() def get_shipment(self, shipment_id): return self.shipping_client.service.GetShipment(shipment_id) @@ -196,4 +202,3 @@ def download_manifest(self, collection_id, download_path, manifest_format=None): manifest_data = r.content with open(download_path, 'wb') as manifest_file: manifest_file.write(manifest_data) - From 5ec9e33893457b4210dcfaa0293f71292864a4ae Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Thu, 11 Apr 2019 16:24:42 +0100 Subject: [PATCH 11/41] non-pdf return objects --- entities/collection.py | 51 +++++++++++++ entities/collection_date.py | 16 ++++ entities/parcel.py | 1 + entities/recipient.py | 2 +- entities/sender.py | 2 +- entities/{shipment.py => shipment_request.py} | 0 entities/shipment_return.py | 74 +++++++++++++++++++ library/DBPAPI.py | 60 ++++++++++++--- 8 files changed, 194 insertions(+), 12 deletions(-) create mode 100644 entities/collection.py create mode 100644 entities/collection_date.py rename entities/{shipment.py => shipment_request.py} (100%) create mode 100644 entities/shipment_return.py diff --git a/entities/collection.py b/entities/collection.py new file mode 100644 index 0000000..e860a69 --- /dev/null +++ b/entities/collection.py @@ -0,0 +1,51 @@ +import sender, courier, collection_date + + +class Collection(object): + def __init__(self, client, id=None, document_id=None, collection_type=None, + date=None, sender_address=None, courier=None, labels_url=None, manifest_url=None): + self.client = client + self.type_name = 'ns1:CollectionReturnType' + self.id = id + self.document_id = document_id + self.collection_type=collection_type + self.date=date + self.sender_address=sender_address + self.courier=courier + self.labels_url=labels_url + self.manifest_url=manifest_url + + @classmethod + def from_dict(cls, client, **kwargs): + return cls( + client=client, + id=kwargs.get('CollectionID'), + document_id=kwargs.get('CollectionDocumentID'), + collection_type=kwargs.get('CollectionType'), + date=collection_date.CollectionDate.from_dict( + client, + **client.dict(kwargs.get('CollectionDate')) + ), + sender_address=sender.Sender.from_dict( + client, + **client.dict(kwargs.get('SenderAddress', None)) + ), + courier=courier.Courier.from_dict( + client, + **client.dict(kwargs.get('Courier', None)) + ), + labels_url=kwargs.get('LabelsURL', None), + manifest_url=kwargs.get('Manifest', None) + ) + + def to_soap_object(self): + suds_object = self.client.factory.create(self.type_name) + suds_object.CollectionID = self.id + suds_object.CollectionDocumentID = self.document_id + suds_object.CollectionType = self.collection_type + suds_object.CollectionDate = self.date + suds_object.SenderAddress = self.sender_address + suds_object.Courier = self.courier + suds_object.LabelsURL = self.labels_url + suds_object.Manifest = self.manifest_url + return suds_object diff --git a/entities/collection_date.py b/entities/collection_date.py new file mode 100644 index 0000000..b942918 --- /dev/null +++ b/entities/collection_date.py @@ -0,0 +1,16 @@ +class CollectionDate(object): + def __init__(self, client, date=None): + self.client = client + self.type_name = 'ns1:CollectionDateType' + self.date = date + + @classmethod + def from_dict(cls, client, **kwargs): + return cls( + client=client, + date=kwargs.get('CollectionDate', None) + ) + + def to_soap_object(self): + suds_object = self.client.factory.create(self.type_name) + suds_object.CollectionDate = self.date diff --git a/entities/parcel.py b/entities/parcel.py index 566eb68..406fa96 100644 --- a/entities/parcel.py +++ b/entities/parcel.py @@ -12,6 +12,7 @@ def __init__(self, client, weight=None, length=None, width=None, height=None, co @classmethod def from_dict(cls, client, **kwargs): + print('parcel from dict kwargs', kwargs) return cls( client=client, weight=kwargs.get('Weight', None), diff --git a/entities/recipient.py b/entities/recipient.py index 155e2ca..198928e 100644 --- a/entities/recipient.py +++ b/entities/recipient.py @@ -19,7 +19,7 @@ def from_dict(cls, client, **kwargs): email=kwargs.get('RecipientEmail', None), address=address.Address.from_dict( client, - **client.accounts_client.dict(kwargs.get('RecipientAddress', None)) + **client.dict(kwargs.get('RecipientAddress', None)) ) ) diff --git a/entities/sender.py b/entities/sender.py index 8b62f59..1719813 100644 --- a/entities/sender.py +++ b/entities/sender.py @@ -21,7 +21,7 @@ def from_dict(cls, client, **kwargs): address_id=kwargs.get('SenderAddressID'), address=address.Address.from_dict( client, - **client.accounts_client.dict(kwargs.get('SenderAddress', None)) + **client.dict(kwargs.get('SenderAddress', None)) ) ) diff --git a/entities/shipment.py b/entities/shipment_request.py similarity index 100% rename from entities/shipment.py rename to entities/shipment_request.py diff --git a/entities/shipment_return.py b/entities/shipment_return.py new file mode 100644 index 0000000..7383a1b --- /dev/null +++ b/entities/shipment_return.py @@ -0,0 +1,74 @@ +import recipient, sender, parcel + + +class Shipment(object): + def __init__(self, client, shipment_id=None, shipment_document_id=None, collection_id=None, + service_id=None, parcels=None, client_reference=None, recipient_address=None, + is_followed=None, is_printed=None, is_despatched=None, is_delivered=None, + is_cancelled=None, labels_url=None): + self.client = client + self.type_name = 'ns1:ShipmentReturnType' + self.shipment_id = shipment_id + self.shipment_document_id = shipment_document_id + self.collection_id = collection_id + self.service_id = service_id + self.parcels = parcels + self.client_reference = client_reference + self.is_followed = is_followed + self.is_printed = is_printed + self.is_despatched = is_despatched + self.is_delivered = is_delivered + self.is_cancelled = is_cancelled + self.labels_url = labels_url + + @classmethod + def from_dict(cls, client, **kwargs): + parcel_array = [] + for parcel_item in kwargs.get('Parcels'): + parcel_array.append(parcel.Parcel.from_dict( + client, + **client.dict(parcel_item) + )) + return cls( + client=client, + shipment_id=kwargs.get('ShipmentID'), + shipment_document_id=kwargs.get('ShipmentDocumentID'), + collection_id=kwargs.get('CollectionID'), + service_id=kwargs.get('ServiceID'), + parcels=parcel_array, + client_reference=kwargs.get('ClientReference'), + recipient_address=recipient.Recipient.from_dict( + client, + **client.dict(kwargs.get('RecipientAddress', None)) + ), + is_followed=kwargs.get('IsFollowed'), + is_printed=kwargs.get('IsPrinted'), + is_despatched=kwargs.get('IsDespatched'), + is_delivered=kwargs.get('IsDelivered'), + is_cancelled=kwargs.get('IsCancelled'), + labels_url=kwargs.get('LabelsURL', None) + ) + + # todo: decide if needed + # todo: decide if needed + # def to_soap_object(self): + # suds_object = self.client.factory.create(self.type_name) + # parcel_array = self.client.factory.create('ns1:ArrayOfParcelType') + # soap_parcel_list = [] + # for item in self.parcels: + # soap_parcel_list.append(item.to_soap_object()) + # parcel_array.item = soap_parcel_list + # parcel_array._arrayType = "urn:ParcelType[]" + # if isinstance(self.collection_date, str): + # collection_date = self.client.factory.create('CollectionDateType') + # collection_date.CollectionDate = self.collection_date + # else: + # collection_date = self.collection_date + # suds_object.ServiceID = self.service_id + # suds_object.Parcels = parcel_array + # suds_object.ClientReference = self.client_reference + # suds_object.CollectionDate = collection_date + # suds_object.SenderAddress = self.sender_address.to_soap_object() + # suds_object.RecipientAddress = self.recipient_address.to_soap_object() + # suds_object.FollowShipment = self.follow_shipment + # return suds_object diff --git a/library/DBPAPI.py b/library/DBPAPI.py index 71a5ebf..c606393 100644 --- a/library/DBPAPI.py +++ b/library/DBPAPI.py @@ -5,7 +5,7 @@ from suds.client import Client from suds.transport.http import HttpAuthenticated -from entities import parcel, address, recipient, sender, shipment, account, account_balance, address_key, service +from entities import parcel, address, recipient, sender, shipment_request, account, account_balance, address_key, service, collection, shipment_return class DespatchBayAPI(object): @@ -61,7 +61,7 @@ def shipment(self, **kwargs): """ Creates a dbp shipment entity """ - return shipment.Shipment(self.shipping_client, **kwargs) + return shipment_request.Shipment(self.shipping_client, **kwargs) # Account Services @@ -126,29 +126,69 @@ def get_available_services(self, shipment_request): shipment_request.to_soap_object()) available_service_dict_list = [] for available_service in available_services_response: - available_service_dict = self.accounts_client.dict(available_service) + available_service_dict = self.shipping_client.dict(available_service) available_service_dict_list.append(service.Service.from_dict( self.shipping_client, **available_service_dict)) return available_service_dict_list - def get_collection(self, collection_id): - return self.shipping_client.service.GetCollection(collection_id) - def get_available_collection_dates(self, sender_address, courier_id): - return self.shipping_client.service.GetAvailableCollectionDates( + available_collection_dates_response = self.shipping_client.service.GetAvailableCollectionDates( sender_address.to_soap_object(), courier_id) + available_collection_dates_list = [] + for collection_date in available_collection_dates_response: + collection_date_dict = self.shipping_client.dict(collection_date) + available_collection_dates_list.append(collection_date_dict['CollectionDate']) + return available_collection_dates_list + + def get_collection(self, collection_id): + collection = self.shipping_client.service.GetCollection(collection_id) + collection_dict = self.shipping_client.dict(collection) + collection_object = address.Address.from_dict( + self.shipping_client, + **collection_dict + ) + return collection_object + def get_collections(self): - return self.shipping_client.service.GetCollections() + collections = self.shipping_client.service.GetCollections() + collections_dict_list = [] + for found_collection in collections: + collection_dict = self.shipping_client.dict(found_collection) + collections_dict_list.append( + collection.Collection.from_dict( + self.shipping_client, + **collection_dict + ) + ) + return collections_dict_list def get_shipment(self, shipment_id): - return self.shipping_client.service.GetShipment(shipment_id) + shipment_item = self.shipping_client.service.GetShipment(shipment_id) + shipment_dict = self.shipping_client.dict(shipment_item) + collection_object = shipment_return.Shipment.from_dict( + self.shipping_client, + **shipment_dict + ) + return collection_object def add_shipment(self, shipment_request): return self.shipping_client.service.AddShipment(shipment_request.to_soap_object()) def book_shipments(self, shipment_ids): - return self.shipping_client.service.BookShipments(shipment_ids) + array_of_shipment_id = self.shipping_client.factory.create('ns1:ArrayOfShipmentID') + array_of_shipment_id.item = shipment_ids + booked_shipments = self.shipping_client.service.BookShipments(array_of_shipment_id) + booked_shipments_list = [] + for booked_shipment in booked_shipments: + booked_shipment_dict = self.shipping_client.dict(booked_shipment) + booked_shipments_list.append( + shipment_return.Shipment.from_dict( + self.shipping_client, + **booked_shipment_dict + ) + ) + return booked_shipments_list def cancel_shipment(self, shipment_id): return self.shipping_client.service.CancelShipment(shipment_id) From 109d85281901db280c6ea7113fdf7e6d38585ac4 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Fri, 12 Apr 2019 17:35:54 +0100 Subject: [PATCH 12/41] PDF client refactor wip --- entities/pdf.py | 27 ++++++++++++++++++++++++ library/DBPAPI.py | 33 ++++++++++++++--------------- library/pdf_client.py | 48 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 16 deletions(-) create mode 100644 entities/pdf.py create mode 100644 library/pdf_client.py diff --git a/entities/pdf.py b/entities/pdf.py new file mode 100644 index 0000000..1492305 --- /dev/null +++ b/entities/pdf.py @@ -0,0 +1,27 @@ +import base64 + +class Pdf(object): + + def __init__(self, data): + # if self.is_pdf(data): + self.data = data + # else: + # pass + # # print(data) + # # raise TypeError("Apparently not a PDF") + + def is_pdf(self): + return self.data[0:4] == '%PDF' + + def get_raw(self): + return self.data + + def get_base64(self): + return base64.b64decode(self.data) + + #todo how to send to browser + + def download(self, path): + with open(path, 'wb') as pdf_file: + pdf_file.write(self.data) + diff --git a/library/DBPAPI.py b/library/DBPAPI.py index c606393..504daf1 100644 --- a/library/DBPAPI.py +++ b/library/DBPAPI.py @@ -11,7 +11,6 @@ class DespatchBayAPI(object): def __init__(self, apiuser, apikey): - # todo: set differently url = 'http://api.despatchbay.st' soap_path = '/soap/%s/%s?wsdl' documents_path = '/documents/v1/' @@ -19,18 +18,22 @@ def __init__(self, apiuser, apikey): shipping_url = url + soap_path % ('v15', 'shipping') addressing_url = url + soap_path % ('v15', 'addressing') tracking_url = url + soap_path % ('v15', 'tracking') - t1 = HttpAuthenticated(username=apiuser, password=apikey) - t2 = HttpAuthenticated(username=apiuser, password=apikey) - t3 = HttpAuthenticated(username=apiuser, password=apikey) - t4 = HttpAuthenticated(username=apiuser, password=apikey) - self.accounts_client = Client(account_url, transport=t1) - self.addressing_client = Client(addressing_url, transport=t2) - self.shipping_client = Client(shipping_url, transport=t3) - self.tracking_client = Client(tracking_url, transport=t4) + self.accounts_client = Client( + account_url, transport=self.create_transport(apiuser, apikey)) + self.addressing_client = Client( + addressing_url, transport=self.create_transport(apiuser, apikey)) + self.shipping_client = Client( + shipping_url, transport=self.create_transport(apiuser, apikey)) + self.tracking_client = Client( + tracking_url, transport=self.create_transport(apiuser, apikey)) self.labels_url = url + documents_path + 'labels' self.manifest_url = url + documents_path + 'manifest' print(addressing_url) + @staticmethod + def create_transport(username, password): + return HttpAuthenticated(username=username, password=password) + # Shipping entities def parcel(self, **kwargs): @@ -200,8 +203,8 @@ def get_tracking(self, tracking_number): # Labels services - def download_shipment_labels(self, ship_collect_ids, download_path, layout=None, - label_format=None, label_dpi=None): + def get_shipment_labels(self, ship_collect_ids, layout=None, label_format=None, + label_dpi=None): if isinstance(ship_collect_ids, list): shipment_string = ','.join(ship_collect_ids) else: @@ -223,10 +226,9 @@ def download_shipment_labels(self, ship_collect_ids, download_path, layout=None, label_data = base64.b64decode(r.content) else: label_data = r.content - with open(download_path, 'wb') as label_file: - label_file.write(label_data) + return label_data - def download_manifest(self, collection_id, download_path, manifest_format=None): + def get_manifest(self, collection_id, manifest_format=None): query_dict = {} if manifest_format: query_dict['format'] = manifest_format @@ -240,5 +242,4 @@ def download_manifest(self, collection_id, download_path, manifest_format=None): manifest_data = base64.b64decode(r.content) else: manifest_data = r.content - with open(download_path, 'wb') as manifest_file: - manifest_file.write(manifest_data) + return manifest_data diff --git a/library/pdf_client.py b/library/pdf_client.py new file mode 100644 index 0000000..f29e8a4 --- /dev/null +++ b/library/pdf_client.py @@ -0,0 +1,48 @@ +from urllib.parse import urlencode +from entities import pdf +import requests +import base64 + + +class PdfClient(object): + API_URI = 'http://api.despatchbay.st/documents/v1/labels' + + def __init__(self, credentials, user_agent): + self.auth = { + 'apiuser': credentials['apiUser'], + 'apikey': credentials['apiKey'] + } + self.user_agent = user_agent + + def fetch_shipment_labels(self, ship_collect_ids, layout=None, label_format=None, + label_dpi=None): + if isinstance(ship_collect_ids, list): + shipment_string = ','.join(ship_collect_ids) + else: + shipment_string = ship_collect_ids + query_dict = {} + if layout: + query_dict['layout'] = layout + if label_format: + query_dict['format'] = label_format + if label_format == 'png_base64' and label_dpi: + query_dict['dpi'] = label_dpi + label_request_url = '{}/{}'.format(self.API_URI, + shipment_string) + if query_dict: + query_string = urlencode(query_dict) + label_request_url = label_request_url + '?' + query_string + print(label_request_url) + r = requests.get(label_request_url) + if label_format == 'png_base64' or label_format == 'pdf_base64': + label_data = base64.b64decode(r.content) + else: + label_data = r.content + return pdf.Pdf(label_data) + + def fetch_manifest(self, collection_id): + manifest_request_url = '{}/{}'.format(self.API_URI, + collection_id) + r = requests.get(manifest_request_url) + return pdf.Pdf(r.content) + From d07022da8c6c2c2a236a74f660da0c91e27fbb55 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Mon, 15 Apr 2019 15:15:57 +0100 Subject: [PATCH 13/41] Saved some lines, moved pdf client, added exceptions --- entities/pdf.py | 1 + entities/shipment_request.py | 1 - library/DBPAPI.py | 151 ++++++++++++++--------------------- library/exception.py | 21 +++++ library/pdf_client.py | 30 +++++-- 5 files changed, 106 insertions(+), 98 deletions(-) create mode 100644 library/exception.py diff --git a/entities/pdf.py b/entities/pdf.py index 1492305..d22a506 100644 --- a/entities/pdf.py +++ b/entities/pdf.py @@ -5,6 +5,7 @@ class Pdf(object): def __init__(self, data): # if self.is_pdf(data): self.data = data + print(self.data) # else: # pass # # print(data) diff --git a/entities/shipment_request.py b/entities/shipment_request.py index ab9b2aa..13224b3 100644 --- a/entities/shipment_request.py +++ b/entities/shipment_request.py @@ -19,7 +19,6 @@ def to_soap_object(self): soap_parcel_list = [] for item in self.parcels: soap_parcel_list.append(item.to_soap_object()) - print(soap_parcel_list) parcel_array.item = soap_parcel_list parcel_array._arrayType = "urn:ParcelType[]" if isinstance(self.collection_date, str): diff --git a/library/DBPAPI.py b/library/DBPAPI.py index 504daf1..17af813 100644 --- a/library/DBPAPI.py +++ b/library/DBPAPI.py @@ -1,11 +1,19 @@ -from urllib.parse import urlencode -import base64 - -import requests from suds.client import Client from suds.transport.http import HttpAuthenticated -from entities import parcel, address, recipient, sender, shipment_request, account, account_balance, address_key, service, collection, shipment_return + +from entities.parcel import Parcel +from entities.address import Address +from entities.recipient import Recipient +from entities.sender import Sender +from entities.shipment_request import Shipment +from entities.account import Account +from entities.account_balance import AccountBalance +from entities.address_key import AddressKey +from entities.service import Service +from entities.collection import Collection +from entities.shipment_return import Shipment +from library.pdf_client import PdfClient class DespatchBayAPI(object): @@ -28,6 +36,8 @@ def __init__(self, apiuser, apikey): tracking_url, transport=self.create_transport(apiuser, apikey)) self.labels_url = url + documents_path + 'labels' self.manifest_url = url + documents_path + 'manifest' + # todo set user agent + self.pdf_client = PdfClient({'apiUser': apiuser, 'apikey': apikey}, 'some_user_agent') print(addressing_url) @staticmethod @@ -40,54 +50,53 @@ def parcel(self, **kwargs): """ Creates a dbp parcel entity """ - return parcel.Parcel(self.shipping_client, **kwargs) + return Parcel(self.shipping_client, **kwargs) def address(self, **kwargs): """ Creates a dbp address entity """ - return address.Address(self.shipping_client, **kwargs) + return Address(self.shipping_client, **kwargs) def recipient(self, **kwargs): """ Creates a dbp recipient address entity """ - return recipient.Recipient(self.shipping_client, **kwargs) + return Recipient(self.shipping_client, **kwargs) def sender(self, **kwargs): """ Creates a dbp sender address entity """ - return sender.Sender(self.shipping_client, **kwargs) + return Sender(self.shipping_client, **kwargs) def shipment(self, **kwargs): """ Creates a dbp shipment entity """ - return shipment_request.Shipment(self.shipping_client, **kwargs) + return Shipment(self.shipping_client, **kwargs) # Account Services def get_account(self): - account_data = self.accounts_client.service.GetAccount() - account_dict = self.accounts_client.dict(account_data) - account_object = account.Account.from_dict(self.accounts_client, **account_dict) - return account_object + account_dict = self.accounts_client.dict(self.accounts_client.service.GetAccount()) + return Account.from_dict( + self.accounts_client, + **account_dict + ) def get_account_balance(self): - balance_data = self.accounts_client.service.GetAccountBalance() - balance_dict = self.accounts_client.dict(balance_data) - balance_object = account_balance.AccountBalance.from_dict( + balance_dict = self.accounts_client.dict(self.accounts_client.service.GetAccountBalance()) + return AccountBalance.from_dict( self.accounts_client, - **balance_dict) - return balance_object + **balance_dict + ) def get_sender_addresses(self): - sender_addresses_data = self.accounts_client.service.GetSenderAddresses() sender_addresses_dict_list = [] - for sender_address in sender_addresses_data: + for sender_address in self.accounts_client.service.GetSenderAddresses(): sender_address_dict = self.accounts_client.dict(sender_address) - sender_addresses_dict_list.append(sender.Sender.from_dict( + sender_addresses_dict_list.append(Sender.from_dict( self.accounts_client, **sender_address_dict)) return sender_addresses_dict_list @@ -95,29 +104,29 @@ def get_sender_addresses(self): # Addressing Services def find_address(self, postcode, property_string): - found_address = self.addressing_client.service.FindAddress(postcode, property_string) - found_address_dict = self.addressing_client.dict(found_address) - found_address_object = address.Address.from_dict( + found_address_dict = self.addressing_client.dict( + self.addressing_client.service.FindAddress( + postcode, property_string + )) + return Address.from_dict( self.addressing_client, **found_address_dict ) - return found_address_object def get_address_by_key(self, key): - found_address = self.addressing_client.service.GetAddressByKey(key) - found_address_dict = self.addressing_client.dict(found_address) - found_address_object = address.Address.from_dict( + found_address_dict = self.addressing_client.dict( + self.addressing_client.service.GetAddressByKey(key) + ) + return Address.from_dict( self.addressing_client, **found_address_dict ) - return found_address_object def get_address_keys_by_postcode(self, postcode): - address_key_return = self.addressing_client.service.GetAddressKeysByPostcode(postcode) address_keys_dict_list = [] - for soap_address_key in address_key_return: + for soap_address_key in self.addressing_client.service.GetAddressKeysByPostcode(postcode): address_key_dict = self.accounts_client.dict(soap_address_key) - address_keys_dict_list.append(address_key.AddressKey.from_dict( + address_keys_dict_list.append(AddressKey.from_dict( self.addressing_client, **address_key_dict)) return address_keys_dict_list @@ -125,12 +134,11 @@ def get_address_keys_by_postcode(self, postcode): # Shipping services def get_available_services(self, shipment_request): - available_services_response = self.shipping_client.service.GetAvailableServices( - shipment_request.to_soap_object()) available_service_dict_list = [] - for available_service in available_services_response: + for available_service in self.shipping_client.service.GetAvailableServices( + shipment_request.to_soap_object()): available_service_dict = self.shipping_client.dict(available_service) - available_service_dict_list.append(service.Service.from_dict( + available_service_dict_list.append(Service.from_dict( self.shipping_client, **available_service_dict)) return available_service_dict_list @@ -145,21 +153,19 @@ def get_available_collection_dates(self, sender_address, courier_id): return available_collection_dates_list def get_collection(self, collection_id): - collection = self.shipping_client.service.GetCollection(collection_id) - collection_dict = self.shipping_client.dict(collection) - collection_object = address.Address.from_dict( + collection_dict = self.shipping_client.dict( + self.shipping_client.service.GetCollection(collection_id)) + return Address.from_dict( self.shipping_client, **collection_dict ) - return collection_object def get_collections(self): - collections = self.shipping_client.service.GetCollections() collections_dict_list = [] - for found_collection in collections: + for found_collection in self.shipping_client.service.GetCollections(): collection_dict = self.shipping_client.dict(found_collection) collections_dict_list.append( - collection.Collection.from_dict( + Collection.from_dict( self.shipping_client, **collection_dict ) @@ -167,13 +173,12 @@ def get_collections(self): return collections_dict_list def get_shipment(self, shipment_id): - shipment_item = self.shipping_client.service.GetShipment(shipment_id) - shipment_dict = self.shipping_client.dict(shipment_item) - collection_object = shipment_return.Shipment.from_dict( + shipment_dict = self.shipping_client.dict( + self.shipping_client.service.GetShipment(shipment_id)) + return Shipment.from_dict( self.shipping_client, **shipment_dict ) - return collection_object def add_shipment(self, shipment_request): return self.shipping_client.service.AddShipment(shipment_request.to_soap_object()) @@ -181,12 +186,11 @@ def add_shipment(self, shipment_request): def book_shipments(self, shipment_ids): array_of_shipment_id = self.shipping_client.factory.create('ns1:ArrayOfShipmentID') array_of_shipment_id.item = shipment_ids - booked_shipments = self.shipping_client.service.BookShipments(array_of_shipment_id) booked_shipments_list = [] - for booked_shipment in booked_shipments: + for booked_shipment in self.shipping_client.service.BookShipments(array_of_shipment_id): booked_shipment_dict = self.shipping_client.dict(booked_shipment) booked_shipments_list.append( - shipment_return.Shipment.from_dict( + Shipment.from_dict( self.shipping_client, **booked_shipment_dict ) @@ -203,43 +207,8 @@ def get_tracking(self, tracking_number): # Labels services - def get_shipment_labels(self, ship_collect_ids, layout=None, label_format=None, - label_dpi=None): - if isinstance(ship_collect_ids, list): - shipment_string = ','.join(ship_collect_ids) - else: - shipment_string = ship_collect_ids - query_dict = {} - if layout: - query_dict['layout'] = layout - if label_format: - query_dict['format'] = label_format - if label_format == 'png_base64' and label_dpi: - query_dict['dpi'] = label_dpi - label_request_url = '{}/{}'.format(self.labels_url, - shipment_string) - if query_dict: - query_string = urlencode(query_dict) - label_request_url = label_request_url + '?' + query_string - r = requests.get(label_request_url) - if label_format == 'png_base64' or label_format == 'pdf_base64': - label_data = base64.b64decode(r.content) - else: - label_data = r.content - return label_data - - def get_manifest(self, collection_id, manifest_format=None): - query_dict = {} - if manifest_format: - query_dict['format'] = manifest_format - manifest_request_url = '{}/{}'.format(self.manifest_url, - collection_id) - if query_dict: - query_string = urlencode(query_dict) - manifest_request_url = manifest_request_url + '?' + query_string - r = requests.get(manifest_request_url) - if manifest_format == 'base64': - manifest_data = base64.b64decode(r.content) - else: - manifest_data = r.content - return manifest_data + def fetch_shipment_labels(self, document_id, **kwargs): + self.pdf_client.fetch_shipment_labels(document_id, **kwargs) + + def fetch_manifest(self, collection_id): + self.pdf_client.fetch_manifest(collection_id) diff --git a/library/exception.py b/library/exception.py new file mode 100644 index 0000000..1827530 --- /dev/null +++ b/library/exception.py @@ -0,0 +1,21 @@ +from suds import WebFault + +class Error(Exception): + """Base class for other exceptions""" + pass + + +class InvalidArgumentException(Error): + pass + + +class AuthorizationException(Error): + pass + + +class PaymentException(Error): + pass + + +class ApiException(Error): + pass diff --git a/library/pdf_client.py b/library/pdf_client.py index f29e8a4..b28d540 100644 --- a/library/pdf_client.py +++ b/library/pdf_client.py @@ -3,6 +3,8 @@ import requests import base64 +import exception + class PdfClient(object): API_URI = 'http://api.despatchbay.st/documents/v1/labels' @@ -14,8 +16,22 @@ def __init__(self, credentials, user_agent): } self.user_agent = user_agent + def handle_response_code(self, code): + if code == 200: + return True + elif code == 400: + raise exception.InvalidArgumentException('The PDF Labels API was unable to process the request') + elif code == 401: + raise exception.AuthorizationException('Unauthorized') + elif code == 402: + raise exception.PaymentException('Insufficient Despatch Bay account balance') + elif code == 404: + raise exception.ApiException('Unknown shipment ID') + else: + raise exception.ApiException('An unexpected error occurred (HTTP {})'.format(code)) + def fetch_shipment_labels(self, ship_collect_ids, layout=None, label_format=None, - label_dpi=None): + label_dpi=None): if isinstance(ship_collect_ids, list): shipment_string = ','.join(ship_collect_ids) else: @@ -33,16 +49,18 @@ def fetch_shipment_labels(self, ship_collect_ids, layout=None, label_format=None query_string = urlencode(query_dict) label_request_url = label_request_url + '?' + query_string print(label_request_url) - r = requests.get(label_request_url) + response = requests.get(label_request_url) + self.handle_response_code(response.status_code) if label_format == 'png_base64' or label_format == 'pdf_base64': - label_data = base64.b64decode(r.content) + label_data = base64.b64decode(response.content) else: - label_data = r.content + label_data = response.content return pdf.Pdf(label_data) def fetch_manifest(self, collection_id): manifest_request_url = '{}/{}'.format(self.API_URI, collection_id) - r = requests.get(manifest_request_url) - return pdf.Pdf(r.content) + response = requests.get(manifest_request_url) + self.handle_response_code(response.status_code) + return pdf.Pdf(response.content) From 657ed841541e6da5e7322b5d564199c534851613 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Mon, 15 Apr 2019 16:49:30 +0100 Subject: [PATCH 14/41] Added GetServices to account --- entities/account.py | 2 +- entities/pdf.py | 18 ++++++------- entities/shipment_request.py | 2 +- entities/shipment_return.py | 2 +- library/DBPAPI.py | 49 +++++++++++++++++++++++++----------- library/pdf_client.py | 7 +++--- 6 files changed, 47 insertions(+), 33 deletions(-) diff --git a/entities/account.py b/entities/account.py index a97d5e5..9053b3b 100644 --- a/entities/account.py +++ b/entities/account.py @@ -23,7 +23,7 @@ def from_dict(cls, client, **kwargs): name=kwargs.get('AccountName', None), balance=account_balance.AccountBalance.from_dict( client, - **client.accounts_client.dict(kwargs.get('AccountBalance', None)) + **client.dict(kwargs.get('AccountBalance', None)) ) ) diff --git a/entities/pdf.py b/entities/pdf.py index d22a506..2b05eb2 100644 --- a/entities/pdf.py +++ b/entities/pdf.py @@ -3,16 +3,14 @@ class Pdf(object): def __init__(self, data): - # if self.is_pdf(data): - self.data = data - print(self.data) - # else: - # pass - # # print(data) - # # raise TypeError("Apparently not a PDF") + if self.is_pdf(data): + self.data = data + else: + raise TypeError("File returned from api is not a valid PDF.") - def is_pdf(self): - return self.data[0:4] == '%PDF' + @staticmethod + def is_pdf(data): + return data[0:4].decode() == '%PDF' def get_raw(self): return self.data @@ -20,8 +18,6 @@ def get_raw(self): def get_base64(self): return base64.b64decode(self.data) - #todo how to send to browser - def download(self, path): with open(path, 'wb') as pdf_file: pdf_file.write(self.data) diff --git a/entities/shipment_request.py b/entities/shipment_request.py index 13224b3..1782633 100644 --- a/entities/shipment_request.py +++ b/entities/shipment_request.py @@ -1,7 +1,7 @@ import recipient, sender, parcel -class Shipment(object): +class ShipmentRequest(object): def __init__(self, client, service_id=None, parcels=None, client_reference=None, collection_date=None, sender_address=None, recipient_address=None, follow_shipment=None): self.client = client self.type_name = 'ns1:ShipmentRequestType' diff --git a/entities/shipment_return.py b/entities/shipment_return.py index 7383a1b..17fdae1 100644 --- a/entities/shipment_return.py +++ b/entities/shipment_return.py @@ -1,7 +1,7 @@ import recipient, sender, parcel -class Shipment(object): +class ShipmentReturn(object): def __init__(self, client, shipment_id=None, shipment_document_id=None, collection_id=None, service_id=None, parcels=None, client_reference=None, recipient_address=None, is_followed=None, is_printed=None, is_despatched=None, is_delivered=None, diff --git a/library/DBPAPI.py b/library/DBPAPI.py index 17af813..00dc6af 100644 --- a/library/DBPAPI.py +++ b/library/DBPAPI.py @@ -6,19 +6,19 @@ from entities.address import Address from entities.recipient import Recipient from entities.sender import Sender -from entities.shipment_request import Shipment +from entities.shipment_request import ShipmentRequest from entities.account import Account from entities.account_balance import AccountBalance from entities.address_key import AddressKey from entities.service import Service from entities.collection import Collection -from entities.shipment_return import Shipment +from entities.shipment_return import ShipmentReturn from library.pdf_client import PdfClient class DespatchBayAPI(object): - def __init__(self, apiuser, apikey): + def __init__(self, api_user, api_key,): url = 'http://api.despatchbay.st' soap_path = '/soap/%s/%s?wsdl' documents_path = '/documents/v1/' @@ -27,17 +27,16 @@ def __init__(self, apiuser, apikey): addressing_url = url + soap_path % ('v15', 'addressing') tracking_url = url + soap_path % ('v15', 'tracking') self.accounts_client = Client( - account_url, transport=self.create_transport(apiuser, apikey)) + account_url, transport=self.create_transport(api_user, api_key)) self.addressing_client = Client( - addressing_url, transport=self.create_transport(apiuser, apikey)) + addressing_url, transport=self.create_transport(api_user, api_key)) self.shipping_client = Client( - shipping_url, transport=self.create_transport(apiuser, apikey)) + shipping_url, transport=self.create_transport(api_user, api_key)) self.tracking_client = Client( - tracking_url, transport=self.create_transport(apiuser, apikey)) + tracking_url, transport=self.create_transport(api_user, api_key)) self.labels_url = url + documents_path + 'labels' self.manifest_url = url + documents_path + 'manifest' - # todo set user agent - self.pdf_client = PdfClient({'apiUser': apiuser, 'apikey': apikey}, 'some_user_agent') + self.pdf_client = PdfClient({'api_user': api_user, 'api_key': api_key}) print(addressing_url) @staticmethod @@ -70,15 +69,16 @@ def sender(self, **kwargs): """ return Sender(self.shipping_client, **kwargs) - def shipment(self, **kwargs): + def shipment_request(self, **kwargs): """ Creates a dbp shipment entity """ - return Shipment(self.shipping_client, **kwargs) + return ShipmentRequest(self.shipping_client, **kwargs) # Account Services def get_account(self): + """Calls GetAccount from the Despatch Bay Account Service.""" account_dict = self.accounts_client.dict(self.accounts_client.service.GetAccount()) return Account.from_dict( self.accounts_client, @@ -86,6 +86,9 @@ def get_account(self): ) def get_account_balance(self): + """ + Calls GetBalance from the Despatch Bay Account Service. + """ balance_dict = self.accounts_client.dict(self.accounts_client.service.GetAccountBalance()) return AccountBalance.from_dict( self.accounts_client, @@ -93,6 +96,9 @@ def get_account_balance(self): ) def get_sender_addresses(self): + """ + Calls GetSenderAddresses from the Despatch Bay Account Service. + """ sender_addresses_dict_list = [] for sender_address in self.accounts_client.service.GetSenderAddresses(): sender_address_dict = self.accounts_client.dict(sender_address) @@ -101,6 +107,19 @@ def get_sender_addresses(self): **sender_address_dict)) return sender_addresses_dict_list + def get_services(self): + """ + Calls GetServices from the Despatch Bay Account Service. + """ + service_list = [] + for account_service in self.accounts_client.service.GetServices(): + service_list.append( + Service.from_dict( + self.accounts_client, + **self.accounts_client.dict(account_service) + )) + return service_list + # Addressing Services def find_address(self, postcode, property_string): @@ -175,7 +194,7 @@ def get_collections(self): def get_shipment(self, shipment_id): shipment_dict = self.shipping_client.dict( self.shipping_client.service.GetShipment(shipment_id)) - return Shipment.from_dict( + return ShipmentReturn.from_dict( self.shipping_client, **shipment_dict ) @@ -190,7 +209,7 @@ def book_shipments(self, shipment_ids): for booked_shipment in self.shipping_client.service.BookShipments(array_of_shipment_id): booked_shipment_dict = self.shipping_client.dict(booked_shipment) booked_shipments_list.append( - Shipment.from_dict( + ShipmentReturn.from_dict( self.shipping_client, **booked_shipment_dict ) @@ -208,7 +227,7 @@ def get_tracking(self, tracking_number): # Labels services def fetch_shipment_labels(self, document_id, **kwargs): - self.pdf_client.fetch_shipment_labels(document_id, **kwargs) + return self.pdf_client.fetch_shipment_labels(document_id, **kwargs) def fetch_manifest(self, collection_id): - self.pdf_client.fetch_manifest(collection_id) + return self.pdf_client.fetch_manifest(collection_id) diff --git a/library/pdf_client.py b/library/pdf_client.py index b28d540..e71254b 100644 --- a/library/pdf_client.py +++ b/library/pdf_client.py @@ -9,12 +9,11 @@ class PdfClient(object): API_URI = 'http://api.despatchbay.st/documents/v1/labels' - def __init__(self, credentials, user_agent): + def __init__(self, credentials): self.auth = { - 'apiuser': credentials['apiUser'], - 'apikey': credentials['apiKey'] + 'api_user': credentials['api_user'], + 'api_key': credentials['api_key'] } - self.user_agent = user_agent def handle_response_code(self, code): if code == 200: From b1346e7102a6aacb2508fca82be388cc3fa1efb1 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Tue, 16 Apr 2019 11:11:40 +0100 Subject: [PATCH 15/41] Documentation --- library/DBPAPI.py | 58 +++++++++++++++++++++++++++++++++++-------- library/pdf_client.py | 2 +- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/library/DBPAPI.py b/library/DBPAPI.py index 00dc6af..4b9a1c3 100644 --- a/library/DBPAPI.py +++ b/library/DBPAPI.py @@ -18,14 +18,12 @@ class DespatchBayAPI(object): - def __init__(self, api_user, api_key,): - url = 'http://api.despatchbay.st' - soap_path = '/soap/%s/%s?wsdl' + def __init__(self, api_user, api_key, soap_api_url='api.despatchbay.com/soap/v15/%s?wsdl', documents_url=''): documents_path = '/documents/v1/' - account_url = url + soap_path % ('v15', 'account') - shipping_url = url + soap_path % ('v15', 'shipping') - addressing_url = url + soap_path % ('v15', 'addressing') - tracking_url = url + soap_path % ('v15', 'tracking') + account_url = soap_api_url % 'account' + shipping_url = soap_api_url % 'shipping' + addressing_url = soap_api_url % 'addressing' + tracking_url = soap_api_url % 'tracking' self.accounts_client = Client( account_url, transport=self.create_transport(api_user, api_key)) self.addressing_client = Client( @@ -34,8 +32,6 @@ def __init__(self, api_user, api_key,): shipping_url, transport=self.create_transport(api_user, api_key)) self.tracking_client = Client( tracking_url, transport=self.create_transport(api_user, api_key)) - self.labels_url = url + documents_path + 'labels' - self.manifest_url = url + documents_path + 'manifest' self.pdf_client = PdfClient({'api_user': api_user, 'api_key': api_key}) print(addressing_url) @@ -123,6 +119,9 @@ def get_services(self): # Addressing Services def find_address(self, postcode, property_string): + """ + Calls FindAddress from the Despatch Bay Addressing Service. + """ found_address_dict = self.addressing_client.dict( self.addressing_client.service.FindAddress( postcode, property_string @@ -133,6 +132,9 @@ def find_address(self, postcode, property_string): ) def get_address_by_key(self, key): + """ + Calls GetAddressByKey from the Despatch Bay Addressing Service. + """ found_address_dict = self.addressing_client.dict( self.addressing_client.service.GetAddressByKey(key) ) @@ -142,6 +144,9 @@ def get_address_by_key(self, key): ) def get_address_keys_by_postcode(self, postcode): + """ + Calls GetAddressKeysFromPostcode from the Despatch Bay Addressing Service. + """ address_keys_dict_list = [] for soap_address_key in self.addressing_client.service.GetAddressKeysByPostcode(postcode): address_key_dict = self.accounts_client.dict(soap_address_key) @@ -153,6 +158,9 @@ def get_address_keys_by_postcode(self, postcode): # Shipping services def get_available_services(self, shipment_request): + """ + Calls GetAvailableServices from the Despatch Bay Shipping Service. + """ available_service_dict_list = [] for available_service in self.shipping_client.service.GetAvailableServices( shipment_request.to_soap_object()): @@ -163,6 +171,9 @@ def get_available_services(self, shipment_request): return available_service_dict_list def get_available_collection_dates(self, sender_address, courier_id): + """ + Calls GetAvailableCollectionDates from the Despatch Bay Shipping Service. + """ available_collection_dates_response = self.shipping_client.service.GetAvailableCollectionDates( sender_address.to_soap_object(), courier_id) available_collection_dates_list = [] @@ -172,6 +183,9 @@ def get_available_collection_dates(self, sender_address, courier_id): return available_collection_dates_list def get_collection(self, collection_id): + """ + Calls GetCollection from the Despatch Bay Shipping Service. + """ collection_dict = self.shipping_client.dict( self.shipping_client.service.GetCollection(collection_id)) return Address.from_dict( @@ -180,6 +194,9 @@ def get_collection(self, collection_id): ) def get_collections(self): + """ + Calls GetCollections from the Despatch Bay Shipping Service. + """ collections_dict_list = [] for found_collection in self.shipping_client.service.GetCollections(): collection_dict = self.shipping_client.dict(found_collection) @@ -192,6 +209,9 @@ def get_collections(self): return collections_dict_list def get_shipment(self, shipment_id): + """ + Calls GetShipment from the Despatch Bay Shipping Service. + """ shipment_dict = self.shipping_client.dict( self.shipping_client.service.GetShipment(shipment_id)) return ShipmentReturn.from_dict( @@ -200,9 +220,15 @@ def get_shipment(self, shipment_id): ) def add_shipment(self, shipment_request): + """ + Calls AddShipment from the Despatch Bay Shipping Service. + """ return self.shipping_client.service.AddShipment(shipment_request.to_soap_object()) def book_shipments(self, shipment_ids): + """ + Calls BookShipments from the Despatch Bay Shipping Service. + """ array_of_shipment_id = self.shipping_client.factory.create('ns1:ArrayOfShipmentID') array_of_shipment_id.item = shipment_ids booked_shipments_list = [] @@ -217,17 +243,29 @@ def book_shipments(self, shipment_ids): return booked_shipments_list def cancel_shipment(self, shipment_id): + """ + Calls CancelShipment from the Despatch Bay Shipping Service. + """ return self.shipping_client.service.CancelShipment(shipment_id) # Tracking services def get_tracking(self, tracking_number): + """ + Calls GetTracking from the Despatch Bay Tracking Service. + """ return self.tracking_client.service.GetTracking(tracking_number) - # Labels services + # Documents services def fetch_shipment_labels(self, document_id, **kwargs): + """ + Fetches labels from the Despatch Bay documents API. + """ return self.pdf_client.fetch_shipment_labels(document_id, **kwargs) def fetch_manifest(self, collection_id): + """ + Fetches manifests from the Despatch Bay documents API. + """ return self.pdf_client.fetch_manifest(collection_id) diff --git a/library/pdf_client.py b/library/pdf_client.py index e71254b..5bb2a58 100644 --- a/library/pdf_client.py +++ b/library/pdf_client.py @@ -9,7 +9,7 @@ class PdfClient(object): API_URI = 'http://api.despatchbay.st/documents/v1/labels' - def __init__(self, credentials): + def __init__(self, credentials, ): self.auth = { 'api_user': credentials['api_user'], 'api_key': credentials['api_key'] From 1ac97793bbb722514a3206037d13037409d002ba Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Thu, 18 Apr 2019 11:49:09 +0100 Subject: [PATCH 16/41] PaymentMethods --- entities/account.py | 31 ++++----- entities/account_balance.py | 25 +++---- entities/address.py | 27 +++++--- entities/address_key.py | 17 +++-- entities/collection.py | 51 ++++++++------ entities/collection_date.py | 13 +++- entities/courier.py | 19 ++++-- entities/parcel.py | 29 +++++--- entities/payment_method.py | 30 +++++++++ entities/pdf.py | 15 ++++- entities/recipient.py | 29 +++++--- entities/sender.py | 29 +++++--- entities/service.py | 27 +++++--- entities/shipment_request.py | 15 +++-- entities/shipment_return.py | 86 ++++++++++++------------ library/DBPAPI.py | 127 ++++++++++++++++++++--------------- library/pdf_client.py | 42 ++++++------ 17 files changed, 366 insertions(+), 246 deletions(-) create mode 100644 entities/payment_method.py diff --git a/entities/account.py b/entities/account.py index 9053b3b..49bed72 100644 --- a/entities/account.py +++ b/entities/account.py @@ -2,34 +2,35 @@ class Account(object): - arg_map = { - 'AccountID': 'id', - 'AccountName': 'name', - 'AccountBalance': 'balance' - } - - def __init__(self, client, id=None, name=None, balance=None): - self.client = client + def __init__(self, client, account_id=None, name=None, balance=None): + self.account_client = client.account_client self.type_name = 'ns1:AccountType' - self.id = id + self.account_id = account_id self.name = name self.balance = balance @classmethod - def from_dict(cls, client, **kwargs): + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ return cls( client=client, - id=kwargs.get('AccountID', None), - name=kwargs.get('AccountName', None), + account_id=soap_dict.get('AccountID', None), + name=soap_dict.get('AccountName', None), balance=account_balance.AccountBalance.from_dict( client, - **client.dict(kwargs.get('AccountBalance', None)) + client.account_client.dict(soap_dict.get('AccountBalance', None)) ) ) def to_soap_object(self): - suds_object = self.client.factory.create(self.type_name) - suds_object.AccountID = self.id + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.account_client.factory.create(self.type_name) + suds_object.AccountID = self.account_id suds_object.AccountName = self.name suds_object.AccountBalance = self.balance return suds_object diff --git a/entities/account_balance.py b/entities/account_balance.py index 4b9a1d8..ae3c627 100644 --- a/entities/account_balance.py +++ b/entities/account_balance.py @@ -1,26 +1,27 @@ class AccountBalance(object): - arg_map = { - 'Balance': 'balance', - 'AvailableBalance': 'available' - } - def __init__(self, client, balance=None, available=None): - self.client = client + self.account_client = client.account_client self.type_name = 'ns1:BalanceType' self.balance = balance self.available = available @classmethod - def from_dict(cls, client, **kwargs): - print(kwargs) + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ return cls( client=client, - balance=kwargs.get('Balance', None), - available=kwargs.get('AvailableBalance', None) + balance=soap_dict.get('Balance', None), + available=soap_dict.get('AvailableBalance', None) ) def to_soap_object(self): - suds_object = self.client.factory.create(self.type_name) - suds_object.Balance= self.balance + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.account_client.factory.create(self.type_name) + suds_object.Balance = self.balance suds_object.AvailableBalance = self.available return suds_object diff --git a/entities/address.py b/entities/address.py index dcf331a..d1d175c 100644 --- a/entities/address.py +++ b/entities/address.py @@ -1,6 +1,6 @@ class Address(object): def __init__(self, client, company_name, street, locality, town_city, county, postal_code, country_code): - self.client = client + self.addressing_client = client.addressing_client self.type_name = 'ns1:AddressType' self.company_name = company_name self.street = street @@ -11,20 +11,27 @@ def __init__(self, client, company_name, street, locality, town_city, county, po self.country_code = country_code @classmethod - def from_dict(cls, client, **kwargs): + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ return cls( client=client, - company_name=kwargs.get('CompanyName', None), - street=kwargs.get('Street', None), - locality=kwargs.get('Locality', None), - town_city=kwargs.get('TownCity', None), - county=kwargs.get('County', None), - postal_code=kwargs.get('PostalCode', None), - country_code=kwargs.get('CountryCode', None) + company_name=soap_dict.get('CompanyName', None), + street=soap_dict.get('Street', None), + locality=soap_dict.get('Locality', None), + town_city=soap_dict.get('TownCity', None), + county=soap_dict.get('County', None), + postal_code=soap_dict.get('PostalCode', None), + country_code=soap_dict.get('CountryCode', None) ) def to_soap_object(self): - suds_object = self.client.factory.create(self.type_name) + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.addressing_client.factory.create(self.type_name) suds_object.CompanyName = self.company_name suds_object.Street = self.street suds_object.Locality = self.locality diff --git a/entities/address_key.py b/entities/address_key.py index b224bed..08c6428 100644 --- a/entities/address_key.py +++ b/entities/address_key.py @@ -1,20 +1,27 @@ class AddressKey(object): def __init__(self, client, key, address): - self.client = client + self.addressing_client = client.addressing_client self.type_name = 'ns1:AddressKeyType' self.key = key self.address = address @classmethod - def from_dict(cls, client, **kwargs): + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ return cls( client=client, - key=kwargs.get('Key', None), - address=kwargs.get('Address', None) + key=soap_dict.get('Key', None), + address=soap_dict.get('Address', None) ) def to_soap_object(self): - suds_object = self.client.factory.create(self.type_name) + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.addressing_client.factory.create(self.type_name) suds_object.key = self.key suds_object.address = self.address return suds_object diff --git a/entities/collection.py b/entities/collection.py index e860a69..8cb890e 100644 --- a/entities/collection.py +++ b/entities/collection.py @@ -2,50 +2,57 @@ class Collection(object): - def __init__(self, client, id=None, document_id=None, collection_type=None, - date=None, sender_address=None, courier=None, labels_url=None, manifest_url=None): + def __init__(self, client, collection_id=None, document_id=None, collection_type=None, date=None, + sender_address=None, collection_courier=None, labels_url=None, manifest_url=None): self.client = client self.type_name = 'ns1:CollectionReturnType' - self.id = id + self.collection_id = collection_id self.document_id = document_id - self.collection_type=collection_type - self.date=date - self.sender_address=sender_address - self.courier=courier - self.labels_url=labels_url - self.manifest_url=manifest_url + self.collection_type = collection_type + self.date = date + self.sender_address = sender_address + self.collection_courier = collection_courier + self.labels_url = labels_url + self.manifest_url = manifest_url @classmethod - def from_dict(cls, client, **kwargs): + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ return cls( client=client, - id=kwargs.get('CollectionID'), - document_id=kwargs.get('CollectionDocumentID'), - collection_type=kwargs.get('CollectionType'), + collection_id=soap_dict.get('CollectionID'), + document_id=soap_dict.get('CollectionDocumentID'), + collection_type=soap_dict.get('CollectionType'), date=collection_date.CollectionDate.from_dict( client, - **client.dict(kwargs.get('CollectionDate')) + client.dict(soap_dict.get('CollectionDate')) ), sender_address=sender.Sender.from_dict( client, - **client.dict(kwargs.get('SenderAddress', None)) + client.dict(soap_dict.get('SenderAddress', None)) ), - courier=courier.Courier.from_dict( + collection_courier=courier.Courier.from_dict( client, - **client.dict(kwargs.get('Courier', None)) + client.dict(soap_dict.get('Courier', None)) ), - labels_url=kwargs.get('LabelsURL', None), - manifest_url=kwargs.get('Manifest', None) + labels_url=soap_dict.get('LabelsURL', None), + manifest_url=soap_dict.get('Manifest', None) ) def to_soap_object(self): - suds_object = self.client.factory.create(self.type_name) - suds_object.CollectionID = self.id + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.client.shipping_client.factory.create(self.type_name) + suds_object.CollectionID = self.collection_id suds_object.CollectionDocumentID = self.document_id suds_object.CollectionType = self.collection_type suds_object.CollectionDate = self.date suds_object.SenderAddress = self.sender_address - suds_object.Courier = self.courier + suds_object.Courier = self.collection_courier suds_object.LabelsURL = self.labels_url suds_object.Manifest = self.manifest_url return suds_object diff --git a/entities/collection_date.py b/entities/collection_date.py index b942918..6fec37b 100644 --- a/entities/collection_date.py +++ b/entities/collection_date.py @@ -5,12 +5,19 @@ def __init__(self, client, date=None): self.date = date @classmethod - def from_dict(cls, client, **kwargs): + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ return cls( client=client, - date=kwargs.get('CollectionDate', None) + date=soap_dict.get('CollectionDate', None) ) def to_soap_object(self): - suds_object = self.client.factory.create(self.type_name) + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.client.shipping_client.factory.create(self.type_name) suds_object.CollectionDate = self.date diff --git a/entities/courier.py b/entities/courier.py index 5e45ddb..5ea524b 100644 --- a/entities/courier.py +++ b/entities/courier.py @@ -1,20 +1,27 @@ class Courier(object): - def __init__(self,client, id, name): + def __init__(self, client, courier_id, name): self.client = client self.type_name = 'ns1:CourierType' - self.id = id + self.id = courier_id self.name = name @classmethod - def from_dict(cls, client, **kwargs): + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ return cls( client=client, - id=kwargs.get('CourierID', None), - name=kwargs.get('CourierName', None) + courier_id=soap_dict.get('CourierID', None), + name=soap_dict.get('CourierName', None) ) def to_soap_object(self): - suds_object = self.client.factory.create(self.type_name) + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.client.shipping_client.factory.create(self.type_name) suds_object.CourierID= self.id suds_object.CourierName = self.name return suds_object diff --git a/entities/parcel.py b/entities/parcel.py index 406fa96..3659f27 100644 --- a/entities/parcel.py +++ b/entities/parcel.py @@ -1,5 +1,6 @@ class Parcel(object): - def __init__(self, client, weight=None, length=None, width=None, height=None, contents=None, value=None, tracking_number=None): + def __init__(self, client, weight=None, length=None, width=None, height=None, + contents=None, value=None, tracking_number=None): self.client = client self.type_name = 'ns1:ParcelType' self.weight = weight @@ -11,21 +12,27 @@ def __init__(self, client, weight=None, length=None, width=None, height=None, co self.tracking_number = tracking_number @classmethod - def from_dict(cls, client, **kwargs): - print('parcel from dict kwargs', kwargs) + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ return cls( client=client, - weight=kwargs.get('Weight', None), - length=kwargs.get('Length', None), - width=kwargs.get('Width', None), - height=kwargs.get('Height', None), - contents=kwargs.get('Contents', None), - value=kwargs.get('Value', None), - tracking_number=kwargs.get('TrackingNumber', None) + weight=soap_dict.get('Weight', None), + length=soap_dict.get('Length', None), + width=soap_dict.get('Width', None), + height=soap_dict.get('Height', None), + contents=soap_dict.get('Contents', None), + value=soap_dict.get('Value', None), + tracking_number=soap_dict.get('TrackingNumber', None) ) def to_soap_object(self): - suds_object = self.client.factory.create(self.type_name) + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.client.shipping_client.factory.create(self.type_name) suds_object.Weight = self.weight suds_object.Length = self.length suds_object.Width = self.width diff --git a/entities/payment_method.py b/entities/payment_method.py new file mode 100644 index 0000000..cba92fc --- /dev/null +++ b/entities/payment_method.py @@ -0,0 +1,30 @@ +class PaymentMethod(object): + def __init__(self, client, payment_method_id=None, payment_method_type=None, description=None): + self.client = client + self.type_name = 'ns1:PaymentMethod' + self.payment_method_id = payment_method_id + self.type = payment_method_type + self.description = description + + @classmethod + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ + return cls( + client=client, + payment_method_id=soap_dict.get('PaymentMethodID', None), + payment_method_type=soap_dict.get('Type', None), + description=soap_dict.get('Description', None) + ) + + def to_soap_object(self): + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.client.shipping_client.factory.create(self.type_name) + suds_object.PaymentMethodID = self.payment_method_id + suds_object.Type = self.type + suds_object.Description = self.description + return suds_object diff --git a/entities/pdf.py b/entities/pdf.py index 2b05eb2..95dc309 100644 --- a/entities/pdf.py +++ b/entities/pdf.py @@ -1,7 +1,7 @@ import base64 -class Pdf(object): +class Pdf(object): def __init__(self, data): if self.is_pdf(data): self.data = data @@ -10,15 +10,28 @@ def __init__(self, data): @staticmethod def is_pdf(data): + """ + Performs a rudimentary check to see if the data APPEARS to be a + valid POF file. + """ return data[0:4].decode() == '%PDF' def get_raw(self): + """ + Returns the raw data used to create the entity. + """ return self.data def get_base64(self): + """ + Base 64 encodes the PDF data before returning it. + """ return base64.b64decode(self.data) def download(self, path): + """ + Saves the PDF to the specified location. + """ with open(path, 'wb') as pdf_file: pdf_file.write(self.data) diff --git a/entities/recipient.py b/entities/recipient.py index 198928e..3caab9b 100644 --- a/entities/recipient.py +++ b/entities/recipient.py @@ -2,31 +2,38 @@ class Recipient(object): - def __init__(self, client, name=None, telephone=None, email=None, address=None): - self.client = client + def __init__(self, client, name=None, telephone=None, email=None, recipient_address=None): + self.shipping_client = client.shipping_client self.type_name = 'ns1:RecipientAddressType' self.name = name self.telephone = telephone self.email = email - self.address = address + self.recipient_address = recipient_address @classmethod - def from_dict(cls, client, **kwargs): + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ return cls( client=client, - name=kwargs.get('RecipientName', None), - telephone=kwargs.get('RecipientTelephone', None), - email=kwargs.get('RecipientEmail', None), - address=address.Address.from_dict( + name=soap_dict.get('RecipientName', None), + telephone=soap_dict.get('RecipientTelephone', None), + email=soap_dict.get('RecipientEmail', None), + recipient_address=address.Address.from_dict( client, - **client.dict(kwargs.get('RecipientAddress', None)) + client.shipping_client.dict(soap_dict.get('RecipientAddress', None)) ) ) def to_soap_object(self): - suds_object = self.client.factory.create(self.type_name) + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.shipping_client.factory.create(self.type_name) suds_object.RecipientName = self.name suds_object.RecipientTelephone = self.telephone suds_object.RecipientEmail = self.email - suds_object.RecipientAddress = self.address.to_soap_object() + suds_object.RecipientAddress = self.recipient_address.to_soap_object() return suds_object diff --git a/entities/sender.py b/entities/sender.py index 1719813..e208c43 100644 --- a/entities/sender.py +++ b/entities/sender.py @@ -2,36 +2,43 @@ class Sender(object): - def __init__(self, client, name=None, telephone=None, email=None, address=None, address_id=None): + def __init__(self, client, name=None, telephone=None, email=None, sender_address=None, address_id=None): self.client = client self.type_name = 'ns1:SenderAddressType' self.name = name self.telephone = telephone self.email = email self.address_id = address_id - self.address = address + self.sender_address = sender_address @classmethod - def from_dict(cls, client, **kwargs): + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ return cls( client=client, - name=kwargs.get('SenderName', None), - telephone=kwargs.get('SenderTelephone', None), - email=kwargs.get('SenderEmail', None), - address_id=kwargs.get('SenderAddressID'), - address=address.Address.from_dict( + name=soap_dict.get('SenderName', None), + telephone=soap_dict.get('SenderTelephone', None), + email=soap_dict.get('SenderEmail', None), + address_id=soap_dict.get('SenderAddressID'), + sender_address=address.Address.from_dict( client, - **client.dict(kwargs.get('SenderAddress', None)) + client.shipping_client.dict(soap_dict.get('SenderAddress', None)) ) ) def to_soap_object(self): - suds_object = self.client.factory.create(self.type_name) + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.client.shipping_client.factory.create(self.type_name) suds_object.SenderName = self.name suds_object.SenderTelephone = self.telephone suds_object.SenderEmail = self.email if self.address_id: suds_object.SenderAddressID = self.address_id else: - suds_object.SenderAddress = self.address.to_soap_object() + suds_object.SenderAddress = self.sender_address.to_soap_object() return suds_object diff --git a/entities/service.py b/entities/service.py index faf675f..4f1d712 100644 --- a/entities/service.py +++ b/entities/service.py @@ -2,31 +2,38 @@ class Service(object): - def __init__(self,client, id, service_format, name, cost, courier): + def __init__(self,client, service_id, service_format, name, cost, courier): self.client = client self.type_name = 'ns1:CourierType' - self.id = id + self.service_id = service_id self.format = service_format self.name = name self.cost = cost self.courier = courier @classmethod - def from_dict(cls, client, **kwargs): + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ return cls( client=client, - id=kwargs.get('ServiceID', None), - service_format=kwargs.get('Format', None), - name=kwargs.get('Name', None), - cost=kwargs.get('Cost', None), + service_id=soap_dict.get('ServiceID', None), + service_format=soap_dict.get('Format', None), + name=soap_dict.get('Name', None), + cost=soap_dict.get('Cost', None), courier=courier.Courier.from_dict( client, - **client.dict(kwargs.get('Courier', None)) + client.shipping_client.dict(soap_dict.get('Courier', None)) ) ) def to_soap_object(self): - suds_object = self.client.factory.create(self.type_name) - suds_object.CourierID= self.id + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.client.factory.shipping_client.create(self.type_name) + suds_object.CourierID= self.service_id suds_object.CourierName = self.name return suds_object diff --git a/entities/shipment_request.py b/entities/shipment_request.py index 1782633..a20181d 100644 --- a/entities/shipment_request.py +++ b/entities/shipment_request.py @@ -1,8 +1,6 @@ -import recipient, sender, parcel - - class ShipmentRequest(object): - def __init__(self, client, service_id=None, parcels=None, client_reference=None, collection_date=None, sender_address=None, recipient_address=None, follow_shipment=None): + def __init__(self, client, service_id=None, parcels=None, client_reference=None, collection_date=None, + sender_address=None, recipient_address=None, follow_shipment=None): self.client = client self.type_name = 'ns1:ShipmentRequestType' self.service_id = service_id @@ -14,15 +12,18 @@ def __init__(self, client, service_id=None, parcels=None, client_reference=None, self.follow_shipment = follow_shipment def to_soap_object(self): - suds_object = self.client.factory.create(self.type_name) - parcel_array = self.client.factory.create('ns1:ArrayOfParcelType') + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.client.shipping_client.factory.create(self.type_name) + parcel_array = self.client.shipping_client.factory.create('ns1:ArrayOfParcelType') soap_parcel_list = [] for item in self.parcels: soap_parcel_list.append(item.to_soap_object()) parcel_array.item = soap_parcel_list parcel_array._arrayType = "urn:ParcelType[]" if isinstance(self.collection_date, str): - collection_date = self.client.factory.create('CollectionDateType') + collection_date = self.client.shipping_client.factory.create('CollectionDateType') collection_date.CollectionDate = self.collection_date else: collection_date = self.collection_date diff --git a/entities/shipment_return.py b/entities/shipment_return.py index 17fdae1..7370ac8 100644 --- a/entities/shipment_return.py +++ b/entities/shipment_return.py @@ -1,4 +1,5 @@ -import recipient, sender, parcel +import recipient +import parcel class ShipmentReturn(object): @@ -6,7 +7,8 @@ def __init__(self, client, shipment_id=None, shipment_document_id=None, collecti service_id=None, parcels=None, client_reference=None, recipient_address=None, is_followed=None, is_printed=None, is_despatched=None, is_delivered=None, is_cancelled=None, labels_url=None): - self.client = client + self.despatchbay_client = client + self.shipping_client = client.shipping_client self.type_name = 'ns1:ShipmentReturnType' self.shipment_id = shipment_id self.shipment_document_id = shipment_document_id @@ -14,6 +16,7 @@ def __init__(self, client, shipment_id=None, shipment_document_id=None, collecti self.service_id = service_id self.parcels = parcels self.client_reference = client_reference + self.recipient_address = recipient_address self.is_followed = is_followed self.is_printed = is_printed self.is_despatched = is_despatched @@ -22,53 +25,50 @@ def __init__(self, client, shipment_id=None, shipment_document_id=None, collecti self.labels_url = labels_url @classmethod - def from_dict(cls, client, **kwargs): + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ parcel_array = [] - for parcel_item in kwargs.get('Parcels'): - parcel_array.append(parcel.Parcel.from_dict( - client, - **client.dict(parcel_item) - )) + for parcel_item in soap_dict.get('Parcels'): + parcel_array.append( + parcel.Parcel.from_dict( + client, + client.shipping_client.dict(parcel_item) + ) + ) return cls( client=client, - shipment_id=kwargs.get('ShipmentID'), - shipment_document_id=kwargs.get('ShipmentDocumentID'), - collection_id=kwargs.get('CollectionID'), - service_id=kwargs.get('ServiceID'), + shipment_id=soap_dict.get('ShipmentID'), + shipment_document_id=soap_dict.get('ShipmentDocumentID'), + collection_id=soap_dict.get('CollectionID'), + service_id=soap_dict.get('ServiceID'), parcels=parcel_array, - client_reference=kwargs.get('ClientReference'), + client_reference=soap_dict.get('ClientReference'), recipient_address=recipient.Recipient.from_dict( client, - **client.dict(kwargs.get('RecipientAddress', None)) + client.shipping_client.dict(soap_dict.get('RecipientAddress', None)) ), - is_followed=kwargs.get('IsFollowed'), - is_printed=kwargs.get('IsPrinted'), - is_despatched=kwargs.get('IsDespatched'), - is_delivered=kwargs.get('IsDelivered'), - is_cancelled=kwargs.get('IsCancelled'), - labels_url=kwargs.get('LabelsURL', None) + is_followed=soap_dict.get('IsFollowed'), + is_printed=soap_dict.get('IsPrinted'), + is_despatched=soap_dict.get('IsDespatched'), + is_delivered=soap_dict.get('IsDelivered'), + is_cancelled=soap_dict.get('IsCancelled'), + labels_url=soap_dict.get('LabelsURL', None) ) - # todo: decide if needed - # todo: decide if needed - # def to_soap_object(self): - # suds_object = self.client.factory.create(self.type_name) - # parcel_array = self.client.factory.create('ns1:ArrayOfParcelType') - # soap_parcel_list = [] - # for item in self.parcels: - # soap_parcel_list.append(item.to_soap_object()) - # parcel_array.item = soap_parcel_list - # parcel_array._arrayType = "urn:ParcelType[]" - # if isinstance(self.collection_date, str): - # collection_date = self.client.factory.create('CollectionDateType') - # collection_date.CollectionDate = self.collection_date - # else: - # collection_date = self.collection_date - # suds_object.ServiceID = self.service_id - # suds_object.Parcels = parcel_array - # suds_object.ClientReference = self.client_reference - # suds_object.CollectionDate = collection_date - # suds_object.SenderAddress = self.sender_address.to_soap_object() - # suds_object.RecipientAddress = self.recipient_address.to_soap_object() - # suds_object.FollowShipment = self.follow_shipment - # return suds_object + def cancel(self): + """ + Makes a CancelShipment request through the Despatch Bay API client. + """ + cancel_return = self.despatchbay_client.cancel_shipment(self.shipment_id) + if cancel_return: + self.is_cancelled = True + return cancel_return + + def get_labels(self, **kwargs): + """ + Fetches label pdf through the Despatch Bay API client. + """ + return self.despatchbay_client.fetch_shipment_labels(self.shipment_document_id, **kwargs) diff --git a/library/DBPAPI.py b/library/DBPAPI.py index 4b9a1c3..66970b0 100644 --- a/library/DBPAPI.py +++ b/library/DBPAPI.py @@ -2,29 +2,31 @@ from suds.transport.http import HttpAuthenticated -from entities.parcel import Parcel -from entities.address import Address -from entities.recipient import Recipient -from entities.sender import Sender -from entities.shipment_request import ShipmentRequest -from entities.account import Account -from entities.account_balance import AccountBalance -from entities.address_key import AddressKey -from entities.service import Service -from entities.collection import Collection -from entities.shipment_return import ShipmentReturn -from library.pdf_client import PdfClient +from parcel import Parcel +from address import Address +from recipient import Recipient +from sender import Sender +from shipment_request import ShipmentRequest +from account import Account +from account_balance import AccountBalance +from address_key import AddressKey +from service import Service +from collection import Collection +from shipment_return import ShipmentReturn +from pdf_client import PdfClient +from payment_method import PaymentMethod class DespatchBayAPI(object): - def __init__(self, api_user, api_key, soap_api_url='api.despatchbay.com/soap/v15/%s?wsdl', documents_url=''): - documents_path = '/documents/v1/' - account_url = soap_api_url % 'account' - shipping_url = soap_api_url % 'shipping' - addressing_url = soap_api_url % 'addressing' - tracking_url = soap_api_url % 'tracking' - self.accounts_client = Client( + def __init__(self, api_user, api_key, api_domain='api.despatchbay.com', api_version='15'): + soap_url_template = 'http://{}/soap/v{}/{}?wsdl' + documents_url = 'http://{}/documents/v1'.format(api_domain) + account_url = soap_url_template.format(api_domain, api_version, 'account') + shipping_url = soap_url_template.format(api_domain, api_version, 'shipping') + addressing_url = soap_url_template.format(api_domain, api_version, 'addressing') + tracking_url = soap_url_template.format(api_domain, api_version, 'tracking') + self.account_client = Client( account_url, transport=self.create_transport(api_user, api_key)) self.addressing_client = Client( addressing_url, transport=self.create_transport(api_user, api_key)) @@ -32,8 +34,7 @@ def __init__(self, api_user, api_key, soap_api_url='api.despatchbay.com/soap/v15 shipping_url, transport=self.create_transport(api_user, api_key)) self.tracking_client = Client( tracking_url, transport=self.create_transport(api_user, api_key)) - self.pdf_client = PdfClient({'api_user': api_user, 'api_key': api_key}) - print(addressing_url) + self.pdf_client = PdfClient(api_url=documents_url) @staticmethod def create_transport(username, password): @@ -45,50 +46,50 @@ def parcel(self, **kwargs): """ Creates a dbp parcel entity """ - return Parcel(self.shipping_client, **kwargs) + return Parcel(self, **kwargs) def address(self, **kwargs): """ Creates a dbp address entity """ - return Address(self.shipping_client, **kwargs) + return Address(self, **kwargs) def recipient(self, **kwargs): """ Creates a dbp recipient address entity """ - return Recipient(self.shipping_client, **kwargs) + return Recipient(self, **kwargs) def sender(self, **kwargs): """ Creates a dbp sender address entity """ - return Sender(self.shipping_client, **kwargs) + return Sender(self, **kwargs) def shipment_request(self, **kwargs): """ Creates a dbp shipment entity """ - return ShipmentRequest(self.shipping_client, **kwargs) + return ShipmentRequest(self, **kwargs) # Account Services def get_account(self): """Calls GetAccount from the Despatch Bay Account Service.""" - account_dict = self.accounts_client.dict(self.accounts_client.service.GetAccount()) + account_dict = self.account_client.dict(self.account_client.service.GetAccount()) return Account.from_dict( - self.accounts_client, - **account_dict + self, + account_dict ) def get_account_balance(self): """ Calls GetBalance from the Despatch Bay Account Service. """ - balance_dict = self.accounts_client.dict(self.accounts_client.service.GetAccountBalance()) + balance_dict = self.account_client.dict(self.account_client.service.GetAccountBalance()) return AccountBalance.from_dict( - self.accounts_client, - **balance_dict + self, + balance_dict ) def get_sender_addresses(self): @@ -96,11 +97,11 @@ def get_sender_addresses(self): Calls GetSenderAddresses from the Despatch Bay Account Service. """ sender_addresses_dict_list = [] - for sender_address in self.accounts_client.service.GetSenderAddresses(): - sender_address_dict = self.accounts_client.dict(sender_address) + for sender_address in self.account_client.service.GetSenderAddresses(): + sender_address_dict = self.account_client.dict(sender_address) sender_addresses_dict_list.append(Sender.from_dict( - self.accounts_client, - **sender_address_dict)) + self, + sender_address_dict)) return sender_addresses_dict_list def get_services(self): @@ -108,14 +109,28 @@ def get_services(self): Calls GetServices from the Despatch Bay Account Service. """ service_list = [] - for account_service in self.accounts_client.service.GetServices(): + for account_service in self.account_client.service.GetServices(): service_list.append( Service.from_dict( - self.accounts_client, - **self.accounts_client.dict(account_service) + self, + self.account_client.dict(account_service) )) return service_list + def get_payment_methods(self): + """ + Calls GetPaymentMethods from the Despatch Bay Account Service. + """ + payment_methods = [] + for payment_method in self.account_client.service.GetPaymentMethods(): + payment_methods.append( + PaymentMethod.from_dict( + self, + self.account_client.dict(payment_method) + ) + ) + return payment_methods + # Addressing Services def find_address(self, postcode, property_string): @@ -127,8 +142,8 @@ def find_address(self, postcode, property_string): postcode, property_string )) return Address.from_dict( - self.addressing_client, - **found_address_dict + self, + found_address_dict ) def get_address_by_key(self, key): @@ -139,8 +154,8 @@ def get_address_by_key(self, key): self.addressing_client.service.GetAddressByKey(key) ) return Address.from_dict( - self.addressing_client, - **found_address_dict + self, + found_address_dict ) def get_address_keys_by_postcode(self, postcode): @@ -149,10 +164,10 @@ def get_address_keys_by_postcode(self, postcode): """ address_keys_dict_list = [] for soap_address_key in self.addressing_client.service.GetAddressKeysByPostcode(postcode): - address_key_dict = self.accounts_client.dict(soap_address_key) + address_key_dict = self.account_client.dict(soap_address_key) address_keys_dict_list.append(AddressKey.from_dict( - self.addressing_client, - **address_key_dict)) + self, + address_key_dict)) return address_keys_dict_list # Shipping services @@ -166,8 +181,8 @@ def get_available_services(self, shipment_request): shipment_request.to_soap_object()): available_service_dict = self.shipping_client.dict(available_service) available_service_dict_list.append(Service.from_dict( - self.shipping_client, - **available_service_dict)) + self, + available_service_dict)) return available_service_dict_list def get_available_collection_dates(self, sender_address, courier_id): @@ -189,8 +204,8 @@ def get_collection(self, collection_id): collection_dict = self.shipping_client.dict( self.shipping_client.service.GetCollection(collection_id)) return Address.from_dict( - self.shipping_client, - **collection_dict + self, + collection_dict ) def get_collections(self): @@ -202,8 +217,8 @@ def get_collections(self): collection_dict = self.shipping_client.dict(found_collection) collections_dict_list.append( Collection.from_dict( - self.shipping_client, - **collection_dict + self, + collection_dict ) ) return collections_dict_list @@ -215,8 +230,8 @@ def get_shipment(self, shipment_id): shipment_dict = self.shipping_client.dict( self.shipping_client.service.GetShipment(shipment_id)) return ShipmentReturn.from_dict( - self.shipping_client, - **shipment_dict + self, + shipment_dict ) def add_shipment(self, shipment_request): @@ -236,8 +251,8 @@ def book_shipments(self, shipment_ids): booked_shipment_dict = self.shipping_client.dict(booked_shipment) booked_shipments_list.append( ShipmentReturn.from_dict( - self.shipping_client, - **booked_shipment_dict + self, + booked_shipment_dict ) ) return booked_shipments_list diff --git a/library/pdf_client.py b/library/pdf_client.py index 5bb2a58..166c6aa 100644 --- a/library/pdf_client.py +++ b/library/pdf_client.py @@ -1,21 +1,19 @@ from urllib.parse import urlencode from entities import pdf import requests -import base64 import exception class PdfClient(object): - API_URI = 'http://api.despatchbay.st/documents/v1/labels' - - def __init__(self, credentials, ): - self.auth = { - 'api_user': credentials['api_user'], - 'api_key': credentials['api_key'] - } - - def handle_response_code(self, code): + def __init__(self, api_url='http://api.despatchbay.com/documents/v1'): + self.api_url = api_url + + @staticmethod + def handle_response_code(code): + """ + Returns true if code is 200, otherwise raises an appropriate exception. + """ if code == 200: return True elif code == 400: @@ -29,8 +27,10 @@ def handle_response_code(self, code): else: raise exception.ApiException('An unexpected error occurred (HTTP {})'.format(code)) - def fetch_shipment_labels(self, ship_collect_ids, layout=None, label_format=None, - label_dpi=None): + def fetch_shipment_labels(self, ship_collect_ids, layout=None, label_format=None, label_dpi=None): + """ + Returns a pdf entity of the shipment labels identified by ship_collect_ids. + """ if isinstance(ship_collect_ids, list): shipment_string = ','.join(ship_collect_ids) else: @@ -42,24 +42,20 @@ def fetch_shipment_labels(self, ship_collect_ids, layout=None, label_format=None query_dict['format'] = label_format if label_format == 'png_base64' and label_dpi: query_dict['dpi'] = label_dpi - label_request_url = '{}/{}'.format(self.API_URI, - shipment_string) + label_request_url = '{}/labels/{}'.format(self.api_url, + shipment_string) if query_dict: query_string = urlencode(query_dict) label_request_url = label_request_url + '?' + query_string - print(label_request_url) response = requests.get(label_request_url) self.handle_response_code(response.status_code) - if label_format == 'png_base64' or label_format == 'pdf_base64': - label_data = base64.b64decode(response.content) - else: - label_data = response.content - return pdf.Pdf(label_data) + return pdf.Pdf(response.content) def fetch_manifest(self, collection_id): - manifest_request_url = '{}/{}'.format(self.API_URI, - collection_id) + """ + Returns a pdf entity of the shipment manifest identified by collection_id. + """ + manifest_request_url = '{}/manifest/{}'.format(self.api_url, collection_id) response = requests.get(manifest_request_url) self.handle_response_code(response.status_code) return pdf.Pdf(response.content) - From 1d7f2820b3a8d7000db2fe132db14bf56b70b6f8 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Thu, 18 Apr 2019 12:30:13 +0100 Subject: [PATCH 17/41] Auto topup --- entities/automatic_topup_settings.py | 30 ++++++++++++++++++++++++++++ library/DBPAPI.py | 11 ++++++++++ 2 files changed, 41 insertions(+) create mode 100644 entities/automatic_topup_settings.py diff --git a/entities/automatic_topup_settings.py b/entities/automatic_topup_settings.py new file mode 100644 index 0000000..ed09612 --- /dev/null +++ b/entities/automatic_topup_settings.py @@ -0,0 +1,30 @@ +class AutomaticTopupSettings(object): + def __init__(self, client, minimum_balance=None, topup_amount=None, payment_method_id=None): + self.client = client + self.type_name = 'ns1:AutomaticTopupsSettingsRequestType' + self.minimum_balance = minimum_balance + self.topup_amount = topup_amount + self.payment_method_id = payment_method_id + + @classmethod + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ + return cls( + client=client, + minimum_balance=soap_dict.get('MinimumBalance', None), + topup_amount=soap_dict.get('TopupAmount', None), + payment_method_id=soap_dict.get('PaymentMethodID', None) + ) + + def to_soap_object(self): + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.client.account_client.factory.create(self.type_name) + suds_object.MinimumBalance = self.minimum_balance + suds_object.TopupAmount = self.topup_amount + suds_object.PaymentMethodID = self.payment_method_id + return suds_object diff --git a/library/DBPAPI.py b/library/DBPAPI.py index 66970b0..85a9fba 100644 --- a/library/DBPAPI.py +++ b/library/DBPAPI.py @@ -15,6 +15,7 @@ from shipment_return import ShipmentReturn from pdf_client import PdfClient from payment_method import PaymentMethod +from automatic_topup_settings import AutomaticTopupSettings class DespatchBayAPI(object): @@ -131,6 +132,16 @@ def get_payment_methods(self): ) return payment_methods + def enable_automatic_topups(self, minimum_balance=None, topup_amount=None, + payment_method_id=None, automatic_topup_settings_object=None): + if not automatic_topup_settings_object: + automatic_topup_settings_object = AutomaticTopupSettings( + self, minimum_balance, topup_amount, payment_method_id) + return self.account_client.service.EnableAutomaticTopups(automatic_topup_settings_object.to_soap_object()) + + def disable_automatic_topups(self): + return self.account_client.service.DisableAutomaticTopups() + # Addressing Services def find_address(self, postcode, property_string): From 891ea3a99ff7305ca21366111983a7b31f4f1774 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Thu, 18 Apr 2019 14:49:15 +0100 Subject: [PATCH 18/41] Docstrings, directories renamed --- library/{DBPAPI.py => despatchbay_sdk.py} | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) rename library/{DBPAPI.py => despatchbay_sdk.py} (96%) diff --git a/library/DBPAPI.py b/library/despatchbay_sdk.py similarity index 96% rename from library/DBPAPI.py rename to library/despatchbay_sdk.py index 85a9fba..4338057 100644 --- a/library/DBPAPI.py +++ b/library/despatchbay_sdk.py @@ -18,7 +18,7 @@ from automatic_topup_settings import AutomaticTopupSettings -class DespatchBayAPI(object): +class DespatchBaySDK(object): def __init__(self, api_user, api_key, api_domain='api.despatchbay.com', api_version='15'): soap_url_template = 'http://{}/soap/v{}/{}?wsdl' @@ -134,12 +134,21 @@ def get_payment_methods(self): def enable_automatic_topups(self, minimum_balance=None, topup_amount=None, payment_method_id=None, automatic_topup_settings_object=None): + """ + Calls EnableAutomaticTopups from the Despatch Bay Account Service. + + Passing an automatic_topup_settings object takes priority over using individual arguments. + """ if not automatic_topup_settings_object: automatic_topup_settings_object = AutomaticTopupSettings( self, minimum_balance, topup_amount, payment_method_id) - return self.account_client.service.EnableAutomaticTopups(automatic_topup_settings_object.to_soap_object()) + return self.account_client.service.EnableAutomaticTopups( + automatic_topup_settings_object.to_soap_object()) def disable_automatic_topups(self): + """ + Calls DisableAutomaticTopups from the Despatch Bay Account Service. + """ return self.account_client.service.DisableAutomaticTopups() # Addressing Services From 7b2394c1ea2dda9b627580054a5064dcbfc1d564 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Wed, 24 Apr 2019 18:15:42 +0100 Subject: [PATCH 19/41] DRY refactor for to_soap_object(), renamed pdf client to documents, wrapped suds exceptions that can be wrapped. --- .../despatchbay_sdk.py => despatchbay_sdk.py | 81 ++++++++++++++----- library/pdf_client.py => documents_client.py | 11 ++- entities/account.py | 38 +++++---- entities/account_balance.py | 30 ++++--- entities/address.py | 60 +++++++++----- entities/address_key.py | 29 ++++--- entities/automatic_topup_settings.py | 34 +++++--- entities/collection.py | 61 +++++++++----- entities/collection_date.py | 24 +++--- entities/courier.py | 34 ++++---- entities/{pdf.py => document.py} | 0 entities/entity.py | 37 +++++++++ entities/parcel.py | 54 +++++++++---- entities/payment_method.py | 37 +++++---- entities/recipient.py | 2 +- entities/sender.py | 2 +- entities/service.py | 8 +- entities/shipment_request.py | 81 ++++++++++++------- entities/shipment_return.py | 4 +- library/exception.py => exception.py | 10 ++- 20 files changed, 434 insertions(+), 203 deletions(-) rename library/despatchbay_sdk.py => despatchbay_sdk.py (83%) rename library/pdf_client.py => documents_client.py (93%) rename entities/{pdf.py => document.py} (100%) create mode 100644 entities/entity.py rename library/exception.py => exception.py (74%) diff --git a/library/despatchbay_sdk.py b/despatchbay_sdk.py similarity index 83% rename from library/despatchbay_sdk.py rename to despatchbay_sdk.py index 4338057..4754d34 100644 --- a/library/despatchbay_sdk.py +++ b/despatchbay_sdk.py @@ -1,21 +1,47 @@ from suds.client import Client from suds.transport.http import HttpAuthenticated - - -from parcel import Parcel -from address import Address -from recipient import Recipient -from sender import Sender -from shipment_request import ShipmentRequest -from account import Account -from account_balance import AccountBalance -from address_key import AddressKey -from service import Service -from collection import Collection -from shipment_return import ShipmentReturn -from pdf_client import PdfClient -from payment_method import PaymentMethod -from automatic_topup_settings import AutomaticTopupSettings +import suds +from exception import AuthorizationException,\ + ApiException, ConnectionException, RateLimitException +from entities.parcel import Parcel +from entities.address import Address +from entities.recipient import Recipient +from entities.sender import Sender +from entities.shipment_request import ShipmentRequest +from entities.account import Account +from entities.account_balance import AccountBalance +from entities.address_key import AddressKey +from entities.service import Service +from entities.collection import Collection +from entities.shipment_return import ShipmentReturn +from entities.payment_method import PaymentMethod +from entities.automatic_topup_settings import AutomaticTopupSettings +from entities.collection_date import CollectionDate +from documents_client import DocumentsClient + + +def handle_suds_fault(error): + exception_info = error.args[0] + if 'Unauthorized' in exception_info: + raise AuthorizationException('Invalid API credentials') from error + elif 'Could not connect to host' in exception_info: + raise ConnectionException('Failed to connect to the Despatch Bay API') from error + elif 'Your access rate limit for this service has been exceeded' in exception_info: + raise RateLimitException(exception_info) + else: + raise ApiException(error) from error + + +def try_except(fn): + """ + A decorator to catch suds exceptions + """ + def wrapped(*args, **kwargs): + try: + return fn(*args, **kwargs) + except suds.WebFault as detail: + handle_suds_fault(detail) + return wrapped class DespatchBaySDK(object): @@ -35,7 +61,7 @@ def __init__(self, api_user, api_key, api_domain='api.despatchbay.com', api_vers shipping_url, transport=self.create_transport(api_user, api_key)) self.tracking_client = Client( tracking_url, transport=self.create_transport(api_user, api_key)) - self.pdf_client = PdfClient(api_url=documents_url) + self.pdf_client = DocumentsClient(api_url=documents_url) @staticmethod def create_transport(username, password): @@ -75,6 +101,7 @@ def shipment_request(self, **kwargs): # Account Services + @try_except def get_account(self): """Calls GetAccount from the Despatch Bay Account Service.""" account_dict = self.account_client.dict(self.account_client.service.GetAccount()) @@ -83,6 +110,7 @@ def get_account(self): account_dict ) + @try_except def get_account_balance(self): """ Calls GetBalance from the Despatch Bay Account Service. @@ -93,6 +121,7 @@ def get_account_balance(self): balance_dict ) + @try_except def get_sender_addresses(self): """ Calls GetSenderAddresses from the Despatch Bay Account Service. @@ -105,6 +134,7 @@ def get_sender_addresses(self): sender_address_dict)) return sender_addresses_dict_list + @try_except def get_services(self): """ Calls GetServices from the Despatch Bay Account Service. @@ -118,6 +148,7 @@ def get_services(self): )) return service_list + @try_except def get_payment_methods(self): """ Calls GetPaymentMethods from the Despatch Bay Account Service. @@ -132,6 +163,7 @@ def get_payment_methods(self): ) return payment_methods + @try_except def enable_automatic_topups(self, minimum_balance=None, topup_amount=None, payment_method_id=None, automatic_topup_settings_object=None): """ @@ -145,6 +177,7 @@ def enable_automatic_topups(self, minimum_balance=None, topup_amount=None, return self.account_client.service.EnableAutomaticTopups( automatic_topup_settings_object.to_soap_object()) + @try_except def disable_automatic_topups(self): """ Calls DisableAutomaticTopups from the Despatch Bay Account Service. @@ -153,6 +186,7 @@ def disable_automatic_topups(self): # Addressing Services + @try_except def find_address(self, postcode, property_string): """ Calls FindAddress from the Despatch Bay Addressing Service. @@ -166,6 +200,7 @@ def find_address(self, postcode, property_string): found_address_dict ) + @try_except def get_address_by_key(self, key): """ Calls GetAddressByKey from the Despatch Bay Addressing Service. @@ -178,6 +213,7 @@ def get_address_by_key(self, key): found_address_dict ) + @try_except def get_address_keys_by_postcode(self, postcode): """ Calls GetAddressKeysFromPostcode from the Despatch Bay Addressing Service. @@ -192,6 +228,7 @@ def get_address_keys_by_postcode(self, postcode): # Shipping services + @try_except def get_available_services(self, shipment_request): """ Calls GetAvailableServices from the Despatch Bay Shipping Service. @@ -205,6 +242,7 @@ def get_available_services(self, shipment_request): available_service_dict)) return available_service_dict_list + @try_except def get_available_collection_dates(self, sender_address, courier_id): """ Calls GetAvailableCollectionDates from the Despatch Bay Shipping Service. @@ -214,9 +252,10 @@ def get_available_collection_dates(self, sender_address, courier_id): available_collection_dates_list = [] for collection_date in available_collection_dates_response: collection_date_dict = self.shipping_client.dict(collection_date) - available_collection_dates_list.append(collection_date_dict['CollectionDate']) + available_collection_dates_list.append(CollectionDate.from_dict(self, collection_date_dict)) return available_collection_dates_list + @try_except def get_collection(self, collection_id): """ Calls GetCollection from the Despatch Bay Shipping Service. @@ -228,6 +267,7 @@ def get_collection(self, collection_id): collection_dict ) + @try_except def get_collections(self): """ Calls GetCollections from the Despatch Bay Shipping Service. @@ -243,6 +283,7 @@ def get_collections(self): ) return collections_dict_list + @try_except def get_shipment(self, shipment_id): """ Calls GetShipment from the Despatch Bay Shipping Service. @@ -254,12 +295,14 @@ def get_shipment(self, shipment_id): shipment_dict ) + @try_except def add_shipment(self, shipment_request): """ Calls AddShipment from the Despatch Bay Shipping Service. """ return self.shipping_client.service.AddShipment(shipment_request.to_soap_object()) + @try_except def book_shipments(self, shipment_ids): """ Calls BookShipments from the Despatch Bay Shipping Service. @@ -277,6 +320,7 @@ def book_shipments(self, shipment_ids): ) return booked_shipments_list + @try_except def cancel_shipment(self, shipment_id): """ Calls CancelShipment from the Despatch Bay Shipping Service. @@ -285,6 +329,7 @@ def cancel_shipment(self, shipment_id): # Tracking services + @try_except def get_tracking(self, tracking_number): """ Calls GetTracking from the Despatch Bay Tracking Service. diff --git a/library/pdf_client.py b/documents_client.py similarity index 93% rename from library/pdf_client.py rename to documents_client.py index 166c6aa..2b1ed78 100644 --- a/library/pdf_client.py +++ b/documents_client.py @@ -1,11 +1,10 @@ from urllib.parse import urlencode -from entities import pdf -import requests - +from entities import document import exception +import requests -class PdfClient(object): +class DocumentsClient(object): def __init__(self, api_url='http://api.despatchbay.com/documents/v1'): self.api_url = api_url @@ -49,7 +48,7 @@ def fetch_shipment_labels(self, ship_collect_ids, layout=None, label_format=None label_request_url = label_request_url + '?' + query_string response = requests.get(label_request_url) self.handle_response_code(response.status_code) - return pdf.Pdf(response.content) + return document.Pdf(response.content) def fetch_manifest(self, collection_id): """ @@ -58,4 +57,4 @@ def fetch_manifest(self, collection_id): manifest_request_url = '{}/manifest/{}'.format(self.api_url, collection_id) response = requests.get(manifest_request_url) self.handle_response_code(response.status_code) - return pdf.Pdf(response.content) + return document.Pdf(response.content) diff --git a/entities/account.py b/entities/account.py index 49bed72..84d13c0 100644 --- a/entities/account.py +++ b/entities/account.py @@ -1,10 +1,30 @@ -import account_balance +from entities import account_balance +from entities.entity import Entity -class Account(object): +class Account(Entity): + + # todo: entity class + SOAP_MAP = { + 'AccountID': { + 'property': 'account_id', + 'type': 'integer' + }, + 'AccountName': { + 'property': 'name', + 'type': 'string' + }, + 'AccountBalance': { + 'property': 'balance', + 'type': 'entity', + # 'entityClass': 'DespatchBay\Entity\AccountBalance' + } + } + + SOAP_TYPE = 'ns1:AccountType' + def __init__(self, client, account_id=None, name=None, balance=None): - self.account_client = client.account_client - self.type_name = 'ns1:AccountType' + super().__init__(self.SOAP_TYPE, client.account_client, self.SOAP_MAP) self.account_id = account_id self.name = name self.balance = balance @@ -24,13 +44,3 @@ def from_dict(cls, client, soap_dict): client.account_client.dict(soap_dict.get('AccountBalance', None)) ) ) - - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.account_client.factory.create(self.type_name) - suds_object.AccountID = self.account_id - suds_object.AccountName = self.name - suds_object.AccountBalance = self.balance - return suds_object diff --git a/entities/account_balance.py b/entities/account_balance.py index ae3c627..4699614 100644 --- a/entities/account_balance.py +++ b/entities/account_balance.py @@ -1,7 +1,22 @@ -class AccountBalance(object): +from entities.entity import Entity + + +class AccountBalance(Entity): + SOAP_MAP = { + 'Balance': { + 'property': 'balance', + 'type': 'float' + }, + 'AvailableBalance': { + 'property': 'available', + 'type': 'float' + } + } + + SOAP_TYPE = 'ns1:AccountBalanceType' + def __init__(self, client, balance=None, available=None): - self.account_client = client.account_client - self.type_name = 'ns1:BalanceType' + super().__init__(self.SOAP_TYPE, client.account_client, self.SOAP_MAP) self.balance = balance self.available = available @@ -16,12 +31,3 @@ def from_dict(cls, client, soap_dict): balance=soap_dict.get('Balance', None), available=soap_dict.get('AvailableBalance', None) ) - - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.account_client.factory.create(self.type_name) - suds_object.Balance = self.balance - suds_object.AvailableBalance = self.available - return suds_object diff --git a/entities/address.py b/entities/address.py index d1d175c..b8449ec 100644 --- a/entities/address.py +++ b/entities/address.py @@ -1,7 +1,44 @@ -class Address(object): - def __init__(self, client, company_name, street, locality, town_city, county, postal_code, country_code): - self.addressing_client = client.addressing_client - self.type_name = 'ns1:AddressType' +from entities.entity import Entity + + +class Address(Entity): + + SOAP_MAP = { + 'CompanyName': { + 'property': 'company_name', + 'type': 'string', + }, + 'Street': { + 'property': 'street', + 'type': 'string', + }, + 'Locality': { + 'property': 'locality', + 'type': 'string', + }, + 'TownCity': { + 'property': 'town_city', + 'type': 'string', + }, + 'County': { + 'property': 'county', + 'type': 'string', + }, + 'PostalCode': { + 'property': 'postal_code', + 'type': 'string', + }, + 'CountryCode': { + 'property': 'country_code', + 'type': 'string', + } + } + + SOAP_TYPE = 'ns1:AddressType' + + def __init__(self, client, company_name=None, street=None, locality=None, town_city=None, county=None, + postal_code=None, country_code=None): + super().__init__(self.SOAP_TYPE, client.addressing_client, self.SOAP_MAP) self.company_name = company_name self.street = street self.locality = locality @@ -13,7 +50,7 @@ def __init__(self, client, company_name, street, locality, town_city, county, po @classmethod def from_dict(cls, client, soap_dict): """ - Alternative constructor, builds entity object from a dictionary representation of + Alternative initialiser, builds entity object from a dictionary representation of a SOAP response created by the SOAP client. """ return cls( @@ -27,16 +64,3 @@ def from_dict(cls, client, soap_dict): country_code=soap_dict.get('CountryCode', None) ) - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.addressing_client.factory.create(self.type_name) - suds_object.CompanyName = self.company_name - suds_object.Street = self.street - suds_object.Locality = self.locality - suds_object.TownCity = self.town_city - suds_object.County = self.county - suds_object.PostalCode = self.postal_code - suds_object.CountryCode = self.country_code - return suds_object diff --git a/entities/address_key.py b/entities/address_key.py index 08c6428..7f6b1d5 100644 --- a/entities/address_key.py +++ b/entities/address_key.py @@ -1,7 +1,21 @@ -class AddressKey(object): +from entities.entity import Entity + + +class AddressKey(Entity): + SOAP_MAP = { + 'Key': { + 'property': 'key', + 'type': 'string', + }, + 'Address': { + 'property': 'address', + 'type': 'string', + } + } + SOAP_TYPE = 'ns1:AddressKeyType' + def __init__(self, client, key, address): - self.addressing_client = client.addressing_client - self.type_name = 'ns1:AddressKeyType' + super().__init__(self.SOAP_TYPE, client, self.SOAP_MAP) self.key = key self.address = address @@ -16,12 +30,3 @@ def from_dict(cls, client, soap_dict): key=soap_dict.get('Key', None), address=soap_dict.get('Address', None) ) - - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.addressing_client.factory.create(self.type_name) - suds_object.key = self.key - suds_object.address = self.address - return suds_object diff --git a/entities/automatic_topup_settings.py b/entities/automatic_topup_settings.py index ed09612..2f1e0f7 100644 --- a/entities/automatic_topup_settings.py +++ b/entities/automatic_topup_settings.py @@ -1,7 +1,25 @@ -class AutomaticTopupSettings(object): +from entities.entity import Entity + + +class AutomaticTopupSettings(Entity): + SOAP_MAP = { + "MinimumBalance": { + "property": "minimum_balance", + "type": "float" + }, + "TopupAmount": { + "property": "topup_amount", + "type": "float" + }, + "PaymentMethodID": { + "property": "payment_method_id", + "type": "string" + } + } + SOAP_TYPE = 'ns1:AutomaticTopupsSettingsRequestType' + def __init__(self, client, minimum_balance=None, topup_amount=None, payment_method_id=None): - self.client = client - self.type_name = 'ns1:AutomaticTopupsSettingsRequestType' + super().__init__(self.SOAP_TYPE, client.account_client, self.SOAP_MAP) self.minimum_balance = minimum_balance self.topup_amount = topup_amount self.payment_method_id = payment_method_id @@ -18,13 +36,3 @@ def from_dict(cls, client, soap_dict): topup_amount=soap_dict.get('TopupAmount', None), payment_method_id=soap_dict.get('PaymentMethodID', None) ) - - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.client.account_client.factory.create(self.type_name) - suds_object.MinimumBalance = self.minimum_balance - suds_object.TopupAmount = self.topup_amount - suds_object.PaymentMethodID = self.payment_method_id - return suds_object diff --git a/entities/collection.py b/entities/collection.py index 8cb890e..973c382 100644 --- a/entities/collection.py +++ b/entities/collection.py @@ -1,11 +1,49 @@ -import sender, courier, collection_date +from entities import sender, courier, collection_date +from entities.entity import Entity -class Collection(object): +class Collection(Entity): + + SOAP_MAP = { + "CollectionID": { + "property": "collection_id", + "type": "string" + }, + "CollectionDocumentID": { + "property": "document_id", + "type": "string" + }, + "CollectionType": { + "property": "collection_type", + "type": "string" + }, + "CollectionDate": { + "property": "date", + "type": "string" + }, + "SenderAddress": { + "property": "sender_address", + "type": "string" + }, + "Courier": { + "property": "collection_courier", + "type": "string" + }, + "LabelsURL": { + "property": "labels_url", + "type": "string" + }, + "Manifest": { + "property": "manifest_url", + "type": "string" + } + } + + SOAP_TYPE = 'ns1:CollectionReturnType' + def __init__(self, client, collection_id=None, document_id=None, collection_type=None, date=None, sender_address=None, collection_courier=None, labels_url=None, manifest_url=None): - self.client = client - self.type_name = 'ns1:CollectionReturnType' + super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) self.collection_id = collection_id self.document_id = document_id self.collection_type = collection_type @@ -41,18 +79,3 @@ def from_dict(cls, client, soap_dict): labels_url=soap_dict.get('LabelsURL', None), manifest_url=soap_dict.get('Manifest', None) ) - - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.client.shipping_client.factory.create(self.type_name) - suds_object.CollectionID = self.collection_id - suds_object.CollectionDocumentID = self.document_id - suds_object.CollectionType = self.collection_type - suds_object.CollectionDate = self.date - suds_object.SenderAddress = self.sender_address - suds_object.Courier = self.collection_courier - suds_object.LabelsURL = self.labels_url - suds_object.Manifest = self.manifest_url - return suds_object diff --git a/entities/collection_date.py b/entities/collection_date.py index 6fec37b..224b6e6 100644 --- a/entities/collection_date.py +++ b/entities/collection_date.py @@ -1,7 +1,18 @@ -class CollectionDate(object): +from entities.entity import Entity + + +class CollectionDate(Entity): + SOAP_MAP = { + "CollectionDate": { + "property": "date", + "type": "string" + } + } + + SOAP_TYPE = 'ns1:CollectionDateType' + def __init__(self, client, date=None): - self.client = client - self.type_name = 'ns1:CollectionDateType' + super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) self.date = date @classmethod @@ -14,10 +25,3 @@ def from_dict(cls, client, soap_dict): client=client, date=soap_dict.get('CollectionDate', None) ) - - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.client.shipping_client.factory.create(self.type_name) - suds_object.CollectionDate = self.date diff --git a/entities/courier.py b/entities/courier.py index 5ea524b..e863070 100644 --- a/entities/courier.py +++ b/entities/courier.py @@ -1,8 +1,23 @@ -class Courier(object): - def __init__(self, client, courier_id, name): - self.client = client - self.type_name = 'ns1:CourierType' - self.id = courier_id +from entities.entity import Entity + + +class Courier(Entity): + + SOAP_MAP = { + 'CourierID': { + 'property': 'courier_id', + 'type': 'integer' + }, + 'CourierName': { + 'property': 'name', + 'type': 'string' + } + } + SOAP_TYPE = 'ns1:CourierType' + + def __init__(self, client, courier_id=None, name=None): + super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) + self.courier_id = courier_id self.name = name @classmethod @@ -16,12 +31,3 @@ def from_dict(cls, client, soap_dict): courier_id=soap_dict.get('CourierID', None), name=soap_dict.get('CourierName', None) ) - - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.client.shipping_client.factory.create(self.type_name) - suds_object.CourierID= self.id - suds_object.CourierName = self.name - return suds_object diff --git a/entities/pdf.py b/entities/document.py similarity index 100% rename from entities/pdf.py rename to entities/document.py diff --git a/entities/entity.py b/entities/entity.py new file mode 100644 index 0000000..706a8fd --- /dev/null +++ b/entities/entity.py @@ -0,0 +1,37 @@ +class Entity(object): + + def __init__(self, soap_type, soap_client, soap_map): + self.soap_type = soap_type + self.soap_client = soap_client + self.soap_map = soap_map + + def to_soap_object(self): + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.soap_client.factory.create(self.soap_type) + for soap_property in self.soap_map: + if self.soap_map[soap_property]['type'] == 'entity': + setattr( + suds_object, + soap_property, + getattr( + self, + self.soap_map[soap_property]['property']).to_soap_object() + ) + elif self.soap_map[soap_property]['type'] == 'entityArray': + entity_list = [] + for entity in getattr(self, self.soap_map[soap_property]['property']): + entity_list.append(entity.to_soap_object()) + soap_array = self.soap_client.factory.create(self.soap_map[soap_property]['soap_type']) + soap_array.item = entity_list + soap_array._arrayType = 'urn:ArrayType[]' + setattr(suds_object, soap_property, soap_array) + else: + setattr( + suds_object, soap_property, getattr( + self, + self.soap_map[soap_property]['property'] + ) + ) + return suds_object diff --git a/entities/parcel.py b/entities/parcel.py index 3659f27..3ce8203 100644 --- a/entities/parcel.py +++ b/entities/parcel.py @@ -1,8 +1,44 @@ -class Parcel(object): +from entities.entity import Entity + + +class Parcel(Entity): + + SOAP_MAP = { + 'Weight': { + 'property': 'weight', + 'type': 'float' + }, + 'Length': { + 'property': 'length', + 'type': 'float' + }, + 'Width': { + 'property': 'width', + 'type': 'float' + }, + 'Height': { + 'property': 'height', + 'type': 'float' + }, + 'Contents': { + 'property': 'contents', + 'type': 'string' + }, + 'Value': { + 'property': 'value', + 'type': 'float' + }, + 'TrackingNumber': { + 'property': 'tracking_number', + 'type': 'string' + } + } + SOAP_TYPE = 'ns1:ParcelType' + def __init__(self, client, weight=None, length=None, width=None, height=None, contents=None, value=None, tracking_number=None): + super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) self.client = client - self.type_name = 'ns1:ParcelType' self.weight = weight self.length = length self.width = width @@ -27,17 +63,3 @@ def from_dict(cls, client, soap_dict): value=soap_dict.get('Value', None), tracking_number=soap_dict.get('TrackingNumber', None) ) - - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.client.shipping_client.factory.create(self.type_name) - suds_object.Weight = self.weight - suds_object.Length = self.length - suds_object.Width = self.width - suds_object.Height = self.height - suds_object.Contents = self.contents - suds_object.Value = self.value - suds_object.TrackingNumber = self.tracking_number - return suds_object diff --git a/entities/payment_method.py b/entities/payment_method.py index cba92fc..2e809f3 100644 --- a/entities/payment_method.py +++ b/entities/payment_method.py @@ -1,9 +1,28 @@ -class PaymentMethod(object): +from entities.entity import Entity + + +class PaymentMethod(Entity): + + SOAP_MAP = { + 'PaymentMethodID': { + 'property': 'payment_method_id', + 'type': 'integer' + }, + 'Type': { + 'property': 'payment_method_type', + 'type': 'string' + }, + 'Description': { + 'property': 'description', + 'type': 'string' + } + } + SOAP_TYPE = 'ns1:PaymentMethodType' + def __init__(self, client, payment_method_id=None, payment_method_type=None, description=None): - self.client = client - self.type_name = 'ns1:PaymentMethod' + super().__init__(self.SOAP_TYPE, client.account_client, self.SOAP_MAP) self.payment_method_id = payment_method_id - self.type = payment_method_type + self.payment_method_type = payment_method_type self.description = description @classmethod @@ -18,13 +37,3 @@ def from_dict(cls, client, soap_dict): payment_method_type=soap_dict.get('Type', None), description=soap_dict.get('Description', None) ) - - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.client.shipping_client.factory.create(self.type_name) - suds_object.PaymentMethodID = self.payment_method_id - suds_object.Type = self.type - suds_object.Description = self.description - return suds_object diff --git a/entities/recipient.py b/entities/recipient.py index 3caab9b..99d8160 100644 --- a/entities/recipient.py +++ b/entities/recipient.py @@ -1,4 +1,4 @@ -import address +from entities import address class Recipient(object): diff --git a/entities/sender.py b/entities/sender.py index e208c43..2f9b351 100644 --- a/entities/sender.py +++ b/entities/sender.py @@ -1,4 +1,4 @@ -import address +from entities import address class Sender(object): diff --git a/entities/service.py b/entities/service.py index 4f1d712..84a2f8b 100644 --- a/entities/service.py +++ b/entities/service.py @@ -1,10 +1,10 @@ -import courier +from entities import courier class Service(object): - def __init__(self,client, service_id, service_format, name, cost, courier): + def __init__(self, client, service_id, service_format, name, cost, courier): self.client = client - self.type_name = 'ns1:CourierType' + self.type_name = 'ns1:ServiceType' self.service_id = service_id self.format = service_format self.name = name @@ -34,6 +34,6 @@ def to_soap_object(self): Creates a SOAP client object representation of this entity. """ suds_object = self.client.factory.shipping_client.create(self.type_name) - suds_object.CourierID= self.service_id + suds_object.CourierID = self.service_id suds_object.CourierName = self.name return suds_object diff --git a/entities/shipment_request.py b/entities/shipment_request.py index a20181d..28cea9e 100644 --- a/entities/shipment_request.py +++ b/entities/shipment_request.py @@ -1,37 +1,64 @@ -class ShipmentRequest(object): +from entities.collection_date import CollectionDate +from entities.entity import Entity + + +class ShipmentRequest(Entity): + SOAP_MAP = { + 'ServiceID': { + 'property': 'service_id', + 'type': 'string', + }, + 'Parcels': { + 'property': 'parcels', + 'type': 'entityArray', + 'soap_type': 'ns1:ArrayOfParcelType', + }, + 'ClientReference': { + 'property': 'client_reference', + 'type': 'string', + }, + 'CollectionDate': { + 'property': 'collection_date', + 'type': 'entity', + }, + 'RecipientAddress': { + 'property': 'recipient_address', + 'type': 'entity', + }, + 'SenderAddress': { + 'property': 'sender_address', + 'type': 'entity', + }, + 'FollowShipment': { + 'property': 'follow_shipment', + 'type': 'boolean', + } + } + + SOAP_TYPE = 'ns1:ShipmentRequestType' + def __init__(self, client, service_id=None, parcels=None, client_reference=None, collection_date=None, sender_address=None, recipient_address=None, follow_shipment=None): - self.client = client - self.type_name = 'ns1:ShipmentRequestType' + super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) + self._despatchbay_client = client self.service_id = service_id self.parcels = parcels self.client_reference = client_reference - self.collection_date = collection_date + self._collection_date = self.validate_collection_date_object(collection_date) self.sender_address = sender_address self.recipient_address = recipient_address self.follow_shipment = follow_shipment - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.client.shipping_client.factory.create(self.type_name) - parcel_array = self.client.shipping_client.factory.create('ns1:ArrayOfParcelType') - soap_parcel_list = [] - for item in self.parcels: - soap_parcel_list.append(item.to_soap_object()) - parcel_array.item = soap_parcel_list - parcel_array._arrayType = "urn:ParcelType[]" - if isinstance(self.collection_date, str): - collection_date = self.client.shipping_client.factory.create('CollectionDateType') - collection_date.CollectionDate = self.collection_date + def validate_collection_date_object(self, collection_date): + if isinstance(collection_date, str): + return CollectionDate(self._despatchbay_client, date=collection_date) else: - collection_date = self.collection_date - suds_object.ServiceID = self.service_id - suds_object.Parcels = parcel_array - suds_object.ClientReference = self.client_reference - suds_object.CollectionDate = collection_date - suds_object.SenderAddress = self.sender_address.to_soap_object() - suds_object.RecipientAddress = self.recipient_address.to_soap_object() - suds_object.FollowShipment = self.follow_shipment - return suds_object + return collection_date + + @property + def collection_date(self): + return self._collection_date + + @collection_date.setter + def collection_date(self, collection_date): + self._collection_date = self.validate_collection_date_object(collection_date) diff --git a/entities/shipment_return.py b/entities/shipment_return.py index 7370ac8..f8179b2 100644 --- a/entities/shipment_return.py +++ b/entities/shipment_return.py @@ -1,5 +1,5 @@ -import recipient -import parcel +from entities import recipient +from entities import parcel class ShipmentReturn(object): diff --git a/library/exception.py b/exception.py similarity index 74% rename from library/exception.py rename to exception.py index 1827530..4ac7cad 100644 --- a/library/exception.py +++ b/exception.py @@ -1,5 +1,3 @@ -from suds import WebFault - class Error(Exception): """Base class for other exceptions""" pass @@ -19,3 +17,11 @@ class PaymentException(Error): class ApiException(Error): pass + + +class ConnectionException(Error): + pass + + +class RateLimitException(Error): + pass From 3b4e04dcc2d6f530ba08cd2df3f8f136731cc5ef Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Thu, 25 Apr 2019 10:50:09 +0100 Subject: [PATCH 20/41] Restructure --- {entities => despatchbay}/__init__.py | 0 .../despatchbay_sdk.py | 30 ++++++------ .../documents_client.py | 2 +- {library => despatchbay/entities}/__init__.py | 0 {entities => despatchbay/entities}/account.py | 4 +- .../entities}/account_balance.py | 2 +- {entities => despatchbay/entities}/address.py | 2 +- .../entities}/address_key.py | 2 +- .../entities}/automatic_topup_settings.py | 2 +- .../entities}/collection.py | 4 +- .../entities}/collection_date.py | 2 +- {entities => despatchbay/entities}/courier.py | 2 +- .../entities}/document.py | 0 {entities => despatchbay/entities}/parcel.py | 2 +- .../entities}/payment_method.py | 2 +- .../entities}/recipient.py | 2 +- {entities => despatchbay/entities}/sender.py | 2 +- {entities => despatchbay/entities}/service.py | 2 +- .../entities}/shipment_request.py | 4 +- .../entities}/shipment_return.py | 4 +- despatchbay/library/__init__.py | 0 {entities => despatchbay/library}/entity.py | 0 .../library/exception.py | 0 despatchbay/tests/__init__.py | 0 despatchbay/tests/pdf_test.py | 7 +++ despatchbay/tests/test_accounts.py | 35 ++++++++++++++ despatchbay/tests/test_addresses.py | 17 +++++++ despatchbay/tests/test_shipping.py | 48 +++++++++++++++++++ 28 files changed, 142 insertions(+), 35 deletions(-) rename {entities => despatchbay}/__init__.py (100%) rename despatchbay_sdk.py => despatchbay/despatchbay_sdk.py (93%) rename documents_client.py => despatchbay/documents_client.py (98%) rename {library => despatchbay/entities}/__init__.py (100%) rename {entities => despatchbay/entities}/account.py (94%) rename {entities => despatchbay/entities}/account_balance.py (96%) rename {entities => despatchbay/entities}/address.py (98%) rename {entities => despatchbay/entities}/address_key.py (95%) rename {entities => despatchbay/entities}/automatic_topup_settings.py (97%) rename {entities => despatchbay/entities}/collection.py (96%) rename {entities => despatchbay/entities}/collection_date.py (95%) rename {entities => despatchbay/entities}/courier.py (96%) rename {entities => despatchbay/entities}/document.py (100%) rename {entities => despatchbay/entities}/parcel.py (98%) rename {entities => despatchbay/entities}/payment_method.py (97%) rename {entities => despatchbay/entities}/recipient.py (97%) rename {entities => despatchbay/entities}/sender.py (97%) rename {entities => despatchbay/entities}/service.py (96%) rename {entities => despatchbay/entities}/shipment_request.py (95%) rename {entities => despatchbay/entities}/shipment_return.py (97%) create mode 100644 despatchbay/library/__init__.py rename {entities => despatchbay/library}/entity.py (100%) rename exception.py => despatchbay/library/exception.py (100%) create mode 100644 despatchbay/tests/__init__.py create mode 100644 despatchbay/tests/pdf_test.py create mode 100644 despatchbay/tests/test_accounts.py create mode 100644 despatchbay/tests/test_addresses.py create mode 100644 despatchbay/tests/test_shipping.py diff --git a/entities/__init__.py b/despatchbay/__init__.py similarity index 100% rename from entities/__init__.py rename to despatchbay/__init__.py diff --git a/despatchbay_sdk.py b/despatchbay/despatchbay_sdk.py similarity index 93% rename from despatchbay_sdk.py rename to despatchbay/despatchbay_sdk.py index 4754d34..327c72e 100644 --- a/despatchbay_sdk.py +++ b/despatchbay/despatchbay_sdk.py @@ -3,21 +3,21 @@ import suds from exception import AuthorizationException,\ ApiException, ConnectionException, RateLimitException -from entities.parcel import Parcel -from entities.address import Address -from entities.recipient import Recipient -from entities.sender import Sender -from entities.shipment_request import ShipmentRequest -from entities.account import Account -from entities.account_balance import AccountBalance -from entities.address_key import AddressKey -from entities.service import Service -from entities.collection import Collection -from entities.shipment_return import ShipmentReturn -from entities.payment_method import PaymentMethod -from entities.automatic_topup_settings import AutomaticTopupSettings -from entities.collection_date import CollectionDate -from documents_client import DocumentsClient +from despatchbay.entities.parcel import Parcel +from despatchbay.entities.address import Address +from despatchbay.entities.recipient import Recipient +from despatchbay.entities.sender import Sender +from despatchbay.entities.shipment_request import ShipmentRequest +from despatchbay.entities.account import Account +from despatchbay.entities.account_balance import AccountBalance +from despatchbay.entities.address_key import AddressKey +from despatchbay.entities.service import Service +from despatchbay.entities.collection import Collection +from despatchbay.entities.shipment_return import ShipmentReturn +from despatchbay.entities.payment_method import PaymentMethod +from despatchbay.entities.automatic_topup_settings import AutomaticTopupSettings +from despatchbay.entities.collection_date import CollectionDate +from despatchbay.documents_client import DocumentsClient def handle_suds_fault(error): diff --git a/documents_client.py b/despatchbay/documents_client.py similarity index 98% rename from documents_client.py rename to despatchbay/documents_client.py index 2b1ed78..150d9b4 100644 --- a/documents_client.py +++ b/despatchbay/documents_client.py @@ -1,5 +1,5 @@ from urllib.parse import urlencode -from entities import document +from despatchbay.entities import document import exception import requests diff --git a/library/__init__.py b/despatchbay/entities/__init__.py similarity index 100% rename from library/__init__.py rename to despatchbay/entities/__init__.py diff --git a/entities/account.py b/despatchbay/entities/account.py similarity index 94% rename from entities/account.py rename to despatchbay/entities/account.py index 84d13c0..0c40d1c 100644 --- a/entities/account.py +++ b/despatchbay/entities/account.py @@ -1,5 +1,5 @@ -from entities import account_balance -from entities.entity import Entity +from despatchbay.entities import account_balance +from entity import Entity class Account(Entity): diff --git a/entities/account_balance.py b/despatchbay/entities/account_balance.py similarity index 96% rename from entities/account_balance.py rename to despatchbay/entities/account_balance.py index 4699614..54bb67d 100644 --- a/entities/account_balance.py +++ b/despatchbay/entities/account_balance.py @@ -1,4 +1,4 @@ -from entities.entity import Entity +from entity import Entity class AccountBalance(Entity): diff --git a/entities/address.py b/despatchbay/entities/address.py similarity index 98% rename from entities/address.py rename to despatchbay/entities/address.py index b8449ec..5852bc6 100644 --- a/entities/address.py +++ b/despatchbay/entities/address.py @@ -1,4 +1,4 @@ -from entities.entity import Entity +from entity import Entity class Address(Entity): diff --git a/entities/address_key.py b/despatchbay/entities/address_key.py similarity index 95% rename from entities/address_key.py rename to despatchbay/entities/address_key.py index 7f6b1d5..9a53da4 100644 --- a/entities/address_key.py +++ b/despatchbay/entities/address_key.py @@ -1,4 +1,4 @@ -from entities.entity import Entity +from entity import Entity class AddressKey(Entity): diff --git a/entities/automatic_topup_settings.py b/despatchbay/entities/automatic_topup_settings.py similarity index 97% rename from entities/automatic_topup_settings.py rename to despatchbay/entities/automatic_topup_settings.py index 2f1e0f7..88005fd 100644 --- a/entities/automatic_topup_settings.py +++ b/despatchbay/entities/automatic_topup_settings.py @@ -1,4 +1,4 @@ -from entities.entity import Entity +from entity import Entity class AutomaticTopupSettings(Entity): diff --git a/entities/collection.py b/despatchbay/entities/collection.py similarity index 96% rename from entities/collection.py rename to despatchbay/entities/collection.py index 973c382..35ff597 100644 --- a/entities/collection.py +++ b/despatchbay/entities/collection.py @@ -1,5 +1,5 @@ -from entities import sender, courier, collection_date -from entities.entity import Entity +from despatchbay.entities import sender, courier, collection_date +from entity import Entity class Collection(Entity): diff --git a/entities/collection_date.py b/despatchbay/entities/collection_date.py similarity index 95% rename from entities/collection_date.py rename to despatchbay/entities/collection_date.py index 224b6e6..6516937 100644 --- a/entities/collection_date.py +++ b/despatchbay/entities/collection_date.py @@ -1,4 +1,4 @@ -from entities.entity import Entity +from entity import Entity class CollectionDate(Entity): diff --git a/entities/courier.py b/despatchbay/entities/courier.py similarity index 96% rename from entities/courier.py rename to despatchbay/entities/courier.py index e863070..0ca5a67 100644 --- a/entities/courier.py +++ b/despatchbay/entities/courier.py @@ -1,4 +1,4 @@ -from entities.entity import Entity +from entity import Entity class Courier(Entity): diff --git a/entities/document.py b/despatchbay/entities/document.py similarity index 100% rename from entities/document.py rename to despatchbay/entities/document.py diff --git a/entities/parcel.py b/despatchbay/entities/parcel.py similarity index 98% rename from entities/parcel.py rename to despatchbay/entities/parcel.py index 3ce8203..c06371a 100644 --- a/entities/parcel.py +++ b/despatchbay/entities/parcel.py @@ -1,4 +1,4 @@ -from entities.entity import Entity +from entity import Entity class Parcel(Entity): diff --git a/entities/payment_method.py b/despatchbay/entities/payment_method.py similarity index 97% rename from entities/payment_method.py rename to despatchbay/entities/payment_method.py index 2e809f3..028266c 100644 --- a/entities/payment_method.py +++ b/despatchbay/entities/payment_method.py @@ -1,4 +1,4 @@ -from entities.entity import Entity +from entity import Entity class PaymentMethod(Entity): diff --git a/entities/recipient.py b/despatchbay/entities/recipient.py similarity index 97% rename from entities/recipient.py rename to despatchbay/entities/recipient.py index 99d8160..a841a5c 100644 --- a/entities/recipient.py +++ b/despatchbay/entities/recipient.py @@ -1,4 +1,4 @@ -from entities import address +from despatchbay.entities import address class Recipient(object): diff --git a/entities/sender.py b/despatchbay/entities/sender.py similarity index 97% rename from entities/sender.py rename to despatchbay/entities/sender.py index 2f9b351..35aa1ac 100644 --- a/entities/sender.py +++ b/despatchbay/entities/sender.py @@ -1,4 +1,4 @@ -from entities import address +from despatchbay.entities import address class Sender(object): diff --git a/entities/service.py b/despatchbay/entities/service.py similarity index 96% rename from entities/service.py rename to despatchbay/entities/service.py index 84a2f8b..49ccbf6 100644 --- a/entities/service.py +++ b/despatchbay/entities/service.py @@ -1,4 +1,4 @@ -from entities import courier +from despatchbay.entities import courier class Service(object): diff --git a/entities/shipment_request.py b/despatchbay/entities/shipment_request.py similarity index 95% rename from entities/shipment_request.py rename to despatchbay/entities/shipment_request.py index 28cea9e..c51764a 100644 --- a/entities/shipment_request.py +++ b/despatchbay/entities/shipment_request.py @@ -1,5 +1,5 @@ -from entities.collection_date import CollectionDate -from entities.entity import Entity +from despatchbay.entities.collection_date import CollectionDate +from entity import Entity class ShipmentRequest(Entity): diff --git a/entities/shipment_return.py b/despatchbay/entities/shipment_return.py similarity index 97% rename from entities/shipment_return.py rename to despatchbay/entities/shipment_return.py index f8179b2..c5f83c2 100644 --- a/entities/shipment_return.py +++ b/despatchbay/entities/shipment_return.py @@ -1,5 +1,5 @@ -from entities import recipient -from entities import parcel +from despatchbay.entities import recipient +from despatchbay.entities import parcel class ShipmentReturn(object): diff --git a/despatchbay/library/__init__.py b/despatchbay/library/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/entities/entity.py b/despatchbay/library/entity.py similarity index 100% rename from entities/entity.py rename to despatchbay/library/entity.py diff --git a/exception.py b/despatchbay/library/exception.py similarity index 100% rename from exception.py rename to despatchbay/library/exception.py diff --git a/despatchbay/tests/__init__.py b/despatchbay/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/despatchbay/tests/pdf_test.py b/despatchbay/tests/pdf_test.py new file mode 100644 index 0000000..90d23d9 --- /dev/null +++ b/despatchbay/tests/pdf_test.py @@ -0,0 +1,7 @@ +# import pdf_client +# +# client = pdf_client.PdfClient({'apiUser': 'foo', 'apiKey': 'bar'}, 'bumagent') +# +# pdf = client.fetch_shipment_labels('s2MQ73VJWh6LS') +# +# print(pdf) \ No newline at end of file diff --git a/despatchbay/tests/test_accounts.py b/despatchbay/tests/test_accounts.py new file mode 100644 index 0000000..45aacff --- /dev/null +++ b/despatchbay/tests/test_accounts.py @@ -0,0 +1,35 @@ +from despatchbay.despatchbay_sdk import DespatchBaySDK +import pprint + +client = DespatchBaySDK(api_user='2MTN-C5I9R355', api_key='114LS25D', api_domain='api.despatchbay.st') +# client = DespatchBaySDK(api_user='D2N3C-7DB9EC51', api_key='676BE721E048AFA69151') +account_return = client.get_account() +print(account_return.__dict__) +print(account_return.to_soap_object()) + +services_return = client.get_services() +for returned_service in services_return: + print(returned_service.__dict__) + +print('='*20) +account_balance = client.get_account_balance() +print(account_balance.balance) +print(account_balance.available) + +print('='*20) +sender_addresses = client.get_sender_addresses() +print(sender_addresses[0].__dict__) +print(sender_addresses[0].sender_address.__dict__) + +print('='*20) +payment_methods = client.get_payment_methods() +for payment_method in payment_methods: + pprint.pprint(payment_method.__dict__) + print(payment_method.to_soap_object()) + +print('='*20) +print('Automatic topup enabled?', client.enable_automatic_topups('100', payment_methods[0].payment_method_id, payment_methods[0].payment_method_id)) + + +print('='*20) +print('Automatic topups disabled?', client.disable_automatic_topups()) diff --git a/despatchbay/tests/test_addresses.py b/despatchbay/tests/test_addresses.py new file mode 100644 index 0000000..3cc4c44 --- /dev/null +++ b/despatchbay/tests/test_addresses.py @@ -0,0 +1,17 @@ +import despatchbay_sdk + +client = despatchbay_sdk.DespatchBaySDK(api_user='2MTN-C5I9R355', api_key='114LS25D', api_domain='api.despatchbay.st') +# client = despatchbay_sdk.DespatchBaySDK(api_user='D2N3C-7DB9EC51', api_key='676BE721E048AFA69151') +print(client.addressing_client) + +address = client.find_address('ln69zd', '17') +print(address.__dict__) +print(address.to_soap_object()) + +address = client.get_address_by_key('wd65jb1015') +print(address.__dict__) +print(address.to_soap_object()) + +for address in client.get_address_keys_by_postcode('wd6 5jb'): + print(address.__dict__) + print(address.to_soap_object()) diff --git a/despatchbay/tests/test_shipping.py b/despatchbay/tests/test_shipping.py new file mode 100644 index 0000000..77e9b14 --- /dev/null +++ b/despatchbay/tests/test_shipping.py @@ -0,0 +1,48 @@ + + + +# normal +client = despatchbay_sdk.DespatchBaySDK(api_user='2MTN-C5I9R355', api_key='114LS25D', api_domain='api.despatchbay.st') +#demo +# self.client = DBPAPI.DespatchBayAPI(apiuser='2MTN-3C7B9E45', apikey='7E33ABC14613E3251846') + +my_parcel_1 = client.parcel(weight=1, length=1, width=1, height=1, contents=1, value=1, tracking_number=None) +print(my_parcel_1.__dict__) +print(my_parcel_1.to_soap_object()) +my_parcel_2 = client.parcel(weight=30, length=99, width=99, height=99, contents='balloons', value=1) +print(my_parcel_2.__dict__) +print(my_parcel_2.to_soap_object()) +my_address = client.address( + company_name="Acme", + country_code="GB", + county="", + locality="", + postal_code="sw1a1aa", + town_city="London", + street="Buckingham Palace" +) +print(my_address.__dict__) +print(my_address.to_soap_object()) +recipient = client.recipient(name="scott", telephone="foo", email="bar", recipient_address=my_address) +sender = client.sender(name="Al", telephone="foo", email="bar", sender_address=my_address) +shipment_request = client.shipment_request(parcels=[my_parcel_1, my_parcel_2], client_reference='puchacz', + collection_date='2019-04-10', sender_address=sender, + recipient_address=recipient, follow_shipment='true') +print(shipment_request.to_soap_object()) +services = client.get_available_services(shipment_request) +for service in services: + print(service.courier.to_soap_object()) +shipment_request.service_id = services[0].service_id +dates = client.get_available_collection_dates(sender, services[0].courier.courier_id) +print(dates) +shipment_request.collection_date = dates[0] +print(shipment_request.__dict__) +print(shipment_request.collection_date.__dict__) +# print('+++++++++++++++++++++') +# print(shipment_request.collection_date.to_soap_object()) +# print('+++++++++++++++++++++') +# added_shipment = client.add_shipment(shipment_request) +# client.book_shipments([added_shipment]) +# shipment_return = client.get_shipment(added_shipment) +# label_pdf = client.fetch_shipment_labels(shipment_return.shipment_document_id) +# label_pdf.download('./new_pdf.pdf') From ee1c961aa5984261b34f2cb07b6b2c895ac7a3bb Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Thu, 25 Apr 2019 18:16:43 +0100 Subject: [PATCH 21/41] Moved entities into single file --- despatchbay/despatchbay_sdk.py | 18 +- despatchbay/documents_client.py | 13 +- despatchbay/entities.py | 780 ++++++++++++++++++ despatchbay/entities/__init__.py | 0 despatchbay/entities/account.py | 46 -- despatchbay/entities/account_balance.py | 33 - despatchbay/entities/address.py | 66 -- despatchbay/entities/address_key.py | 32 - .../entities/automatic_topup_settings.py | 38 - despatchbay/entities/collection.py | 81 -- despatchbay/entities/collection_date.py | 27 - despatchbay/entities/courier.py | 33 - despatchbay/entities/document.py | 37 - despatchbay/entities/parcel.py | 65 -- despatchbay/entities/payment_method.py | 39 - despatchbay/entities/recipient.py | 39 - despatchbay/entities/sender.py | 44 - despatchbay/entities/service.py | 39 - despatchbay/entities/shipment_request.py | 64 -- despatchbay/entities/shipment_return.py | 74 -- .../{library/exception.py => exceptions.py} | 0 despatchbay/library/__init__.py | 0 despatchbay/library/entity.py | 37 - setup.py | 11 + 24 files changed, 800 insertions(+), 816 deletions(-) create mode 100644 despatchbay/entities.py delete mode 100644 despatchbay/entities/__init__.py delete mode 100644 despatchbay/entities/account.py delete mode 100644 despatchbay/entities/account_balance.py delete mode 100644 despatchbay/entities/address.py delete mode 100644 despatchbay/entities/address_key.py delete mode 100644 despatchbay/entities/automatic_topup_settings.py delete mode 100644 despatchbay/entities/collection.py delete mode 100644 despatchbay/entities/collection_date.py delete mode 100644 despatchbay/entities/courier.py delete mode 100644 despatchbay/entities/document.py delete mode 100644 despatchbay/entities/parcel.py delete mode 100644 despatchbay/entities/payment_method.py delete mode 100644 despatchbay/entities/recipient.py delete mode 100644 despatchbay/entities/sender.py delete mode 100644 despatchbay/entities/service.py delete mode 100644 despatchbay/entities/shipment_request.py delete mode 100644 despatchbay/entities/shipment_return.py rename despatchbay/{library/exception.py => exceptions.py} (100%) delete mode 100644 despatchbay/library/__init__.py delete mode 100644 despatchbay/library/entity.py create mode 100644 setup.py diff --git a/despatchbay/despatchbay_sdk.py b/despatchbay/despatchbay_sdk.py index 327c72e..74de0bc 100644 --- a/despatchbay/despatchbay_sdk.py +++ b/despatchbay/despatchbay_sdk.py @@ -1,22 +1,10 @@ from suds.client import Client from suds.transport.http import HttpAuthenticated import suds -from exception import AuthorizationException,\ +from despatchbay.exceptions import AuthorizationException,\ ApiException, ConnectionException, RateLimitException -from despatchbay.entities.parcel import Parcel -from despatchbay.entities.address import Address -from despatchbay.entities.recipient import Recipient -from despatchbay.entities.sender import Sender -from despatchbay.entities.shipment_request import ShipmentRequest -from despatchbay.entities.account import Account -from despatchbay.entities.account_balance import AccountBalance -from despatchbay.entities.address_key import AddressKey -from despatchbay.entities.service import Service -from despatchbay.entities.collection import Collection -from despatchbay.entities.shipment_return import ShipmentReturn -from despatchbay.entities.payment_method import PaymentMethod -from despatchbay.entities.automatic_topup_settings import AutomaticTopupSettings -from despatchbay.entities.collection_date import CollectionDate +from despatchbay.entities import Parcel, Address, Recipient, Sender, ShipmentRequest, Account, AccountBalance,\ + AddressKey, Service, Collection, ShipmentReturn, PaymentMethod, AutomaticTopupSettings, CollectionDate from despatchbay.documents_client import DocumentsClient diff --git a/despatchbay/documents_client.py b/despatchbay/documents_client.py index 150d9b4..7fff01a 100644 --- a/despatchbay/documents_client.py +++ b/despatchbay/documents_client.py @@ -1,6 +1,5 @@ from urllib.parse import urlencode -from despatchbay.entities import document -import exception +from despatchbay import exceptions import requests @@ -16,15 +15,15 @@ def handle_response_code(code): if code == 200: return True elif code == 400: - raise exception.InvalidArgumentException('The PDF Labels API was unable to process the request') + raise exceptions.InvalidArgumentException('The PDF Labels API was unable to process the request') elif code == 401: - raise exception.AuthorizationException('Unauthorized') + raise exceptions.AuthorizationException('Unauthorized') elif code == 402: - raise exception.PaymentException('Insufficient Despatch Bay account balance') + raise exceptions.PaymentException('Insufficient Despatch Bay account balance') elif code == 404: - raise exception.ApiException('Unknown shipment ID') + raise exceptions.ApiException('Unknown shipment ID') else: - raise exception.ApiException('An unexpected error occurred (HTTP {})'.format(code)) + raise exceptions.ApiException('An unexpected error occurred (HTTP {})'.format(code)) def fetch_shipment_labels(self, ship_collect_ids, layout=None, label_format=None, label_dpi=None): """ diff --git a/despatchbay/entities.py b/despatchbay/entities.py new file mode 100644 index 0000000..75f6e32 --- /dev/null +++ b/despatchbay/entities.py @@ -0,0 +1,780 @@ +import base64 + + +class Entity(object): + + def __init__(self, soap_type, soap_client, soap_map): + self.soap_type = soap_type + self.soap_client = soap_client + self.soap_map = soap_map + + def to_soap_object(self): + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.soap_client.factory.create(self.soap_type) + for soap_property in self.soap_map: + if self.soap_map[soap_property]['type'] == 'entity': + setattr( + suds_object, + soap_property, + getattr( + self, + self.soap_map[soap_property]['property']).to_soap_object() + ) + elif self.soap_map[soap_property]['type'] == 'entityArray': + entity_list = [] + for entity in getattr(self, self.soap_map[soap_property]['property']): + entity_list.append(entity.to_soap_object()) + soap_array = self.soap_client.factory.create(self.soap_map[soap_property]['soap_type']) + soap_array.item = entity_list + soap_array._arrayType = 'urn:ArrayType[]' + setattr(suds_object, soap_property, soap_array) + else: + setattr( + suds_object, soap_property, getattr( + self, + self.soap_map[soap_property]['property'] + ) + ) + return suds_object + + +class Account(Entity): + + # todo: entity class + SOAP_MAP = { + 'AccountID': { + 'property': 'account_id', + 'type': 'integer' + }, + 'AccountName': { + 'property': 'name', + 'type': 'string' + }, + 'AccountBalance': { + 'property': 'balance', + 'type': 'entity', + # 'entityClass': 'DespatchBay\Entity\AccountBalance' + } + } + + SOAP_TYPE = 'ns1:AccountType' + + def __init__(self, client, account_id=None, name=None, balance=None): + super().__init__(self.SOAP_TYPE, client.account_client, self.SOAP_MAP) + self.account_id = account_id + self.name = name + self.balance = balance + + @classmethod + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ + return cls( + client=client, + account_id=soap_dict.get('AccountID', None), + name=soap_dict.get('AccountName', None), + balance=AccountBalance.from_dict( + client, + client.account_client.dict(soap_dict.get('AccountBalance', None)) + ) + ) + + +class AccountBalance(Entity): + SOAP_MAP = { + 'Balance': { + 'property': 'balance', + 'type': 'float' + }, + 'AvailableBalance': { + 'property': 'available', + 'type': 'float' + } + } + + SOAP_TYPE = 'ns1:AccountBalanceType' + + def __init__(self, client, balance=None, available=None): + super().__init__(self.SOAP_TYPE, client.account_client, self.SOAP_MAP) + self.balance = balance + self.available = available + + @classmethod + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ + return cls( + client=client, + balance=soap_dict.get('Balance', None), + available=soap_dict.get('AvailableBalance', None) + ) + + +class Address(Entity): + + SOAP_MAP = { + 'CompanyName': { + 'property': 'company_name', + 'type': 'string', + }, + 'Street': { + 'property': 'street', + 'type': 'string', + }, + 'Locality': { + 'property': 'locality', + 'type': 'string', + }, + 'TownCity': { + 'property': 'town_city', + 'type': 'string', + }, + 'County': { + 'property': 'county', + 'type': 'string', + }, + 'PostalCode': { + 'property': 'postal_code', + 'type': 'string', + }, + 'CountryCode': { + 'property': 'country_code', + 'type': 'string', + } + } + + SOAP_TYPE = 'ns1:AddressType' + + def __init__(self, client, company_name=None, street=None, locality=None, town_city=None, county=None, + postal_code=None, country_code=None): + super().__init__(self.SOAP_TYPE, client.addressing_client, self.SOAP_MAP) + self.company_name = company_name + self.street = street + self.locality = locality + self.town_city = town_city + self.county = county + self.postal_code = postal_code + self.country_code = country_code + + @classmethod + def from_dict(cls, client, soap_dict): + """ + Alternative initialiser, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ + return cls( + client=client, + company_name=soap_dict.get('CompanyName', None), + street=soap_dict.get('Street', None), + locality=soap_dict.get('Locality', None), + town_city=soap_dict.get('TownCity', None), + county=soap_dict.get('County', None), + postal_code=soap_dict.get('PostalCode', None), + country_code=soap_dict.get('CountryCode', None) + ) + + +class AddressKey(Entity): + SOAP_MAP = { + 'Key': { + 'property': 'key', + 'type': 'string', + }, + 'Address': { + 'property': 'address', + 'type': 'string', + } + } + SOAP_TYPE = 'ns1:AddressKeyType' + + def __init__(self, client, key, address): + super().__init__(self.SOAP_TYPE, client, self.SOAP_MAP) + self.key = key + self.address = address + + @classmethod + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ + return cls( + client=client, + key=soap_dict.get('Key', None), + address=soap_dict.get('Address', None) + ) + + +class AutomaticTopupSettings(Entity): + SOAP_MAP = { + "MinimumBalance": { + "property": "minimum_balance", + "type": "float" + }, + "TopupAmount": { + "property": "topup_amount", + "type": "float" + }, + "PaymentMethodID": { + "property": "payment_method_id", + "type": "string" + } + } + SOAP_TYPE = 'ns1:AutomaticTopupsSettingsRequestType' + + def __init__(self, client, minimum_balance=None, topup_amount=None, payment_method_id=None): + super().__init__(self.SOAP_TYPE, client.account_client, self.SOAP_MAP) + self.minimum_balance = minimum_balance + self.topup_amount = topup_amount + self.payment_method_id = payment_method_id + + @classmethod + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ + return cls( + client=client, + minimum_balance=soap_dict.get('MinimumBalance', None), + topup_amount=soap_dict.get('TopupAmount', None), + payment_method_id=soap_dict.get('PaymentMethodID', None) + ) + + +class Collection(Entity): + + SOAP_MAP = { + "CollectionID": { + "property": "collection_id", + "type": "string" + }, + "CollectionDocumentID": { + "property": "document_id", + "type": "string" + }, + "CollectionType": { + "property": "collection_type", + "type": "string" + }, + "CollectionDate": { + "property": "date", + "type": "string" + }, + "SenderAddress": { + "property": "sender_address", + "type": "string" + }, + "Courier": { + "property": "collection_courier", + "type": "string" + }, + "LabelsURL": { + "property": "labels_url", + "type": "string" + }, + "Manifest": { + "property": "manifest_url", + "type": "string" + } + } + + SOAP_TYPE = 'ns1:CollectionReturnType' + + def __init__(self, client, collection_id=None, document_id=None, collection_type=None, date=None, + sender_address=None, collection_courier=None, labels_url=None, manifest_url=None): + super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) + self.collection_id = collection_id + self.document_id = document_id + self.collection_type = collection_type + self.date = date + self.sender_address = sender_address + self.collection_courier = collection_courier + self.labels_url = labels_url + self.manifest_url = manifest_url + + @classmethod + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ + return cls( + client=client, + collection_id=soap_dict.get('CollectionID'), + document_id=soap_dict.get('CollectionDocumentID'), + collection_type=soap_dict.get('CollectionType'), + date=CollectionDate.from_dict( + client, + client.dict(soap_dict.get('CollectionDate')) + ), + sender_address=Sender.from_dict( + client, + client.dict(soap_dict.get('SenderAddress', None)) + ), + collection_courier=Courier.from_dict( + client, + client.dict(soap_dict.get('Courier', None)) + ), + labels_url=soap_dict.get('LabelsURL', None), + manifest_url=soap_dict.get('Manifest', None) + ) + + +class CollectionDate(Entity): + SOAP_MAP = { + "CollectionDate": { + "property": "date", + "type": "string" + } + } + + SOAP_TYPE = 'ns1:CollectionDateType' + + def __init__(self, client, date=None): + super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) + self.date = date + + @classmethod + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ + return cls( + client=client, + date=soap_dict.get('CollectionDate', None) + ) + + +class Courier(Entity): + + SOAP_MAP = { + 'CourierID': { + 'property': 'courier_id', + 'type': 'integer' + }, + 'CourierName': { + 'property': 'name', + 'type': 'string' + } + } + SOAP_TYPE = 'ns1:CourierType' + + def __init__(self, client, courier_id=None, name=None): + super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) + self.courier_id = courier_id + self.name = name + + @classmethod + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ + return cls( + client=client, + courier_id=soap_dict.get('CourierID', None), + name=soap_dict.get('CourierName', None) + ) + + +class Document(object): + # todo: dry out or move + def __init__(self, data): + if self.is_pdf(data): + self.data = data + else: + raise TypeError("File returned from api is not a valid PDF.") + + @staticmethod + def is_pdf(data): + """ + Performs a rudimentary check to see if the data APPEARS to be a + valid POF file. + """ + return data[0:4].decode() == '%PDF' + + def get_raw(self): + """ + Returns the raw data used to create the entity. + """ + return self.data + + def get_base64(self): + """ + Base 64 encodes the PDF data before returning it. + """ + return base64.b64decode(self.data) + + def download(self, path): + """ + Saves the PDF to the specified location. + """ + with open(path, 'wb') as pdf_file: + pdf_file.write(self.data) + + +class Parcel(Entity): + + SOAP_MAP = { + 'Weight': { + 'property': 'weight', + 'type': 'float' + }, + 'Length': { + 'property': 'length', + 'type': 'float' + }, + 'Width': { + 'property': 'width', + 'type': 'float' + }, + 'Height': { + 'property': 'height', + 'type': 'float' + }, + 'Contents': { + 'property': 'contents', + 'type': 'string' + }, + 'Value': { + 'property': 'value', + 'type': 'float' + }, + 'TrackingNumber': { + 'property': 'tracking_number', + 'type': 'string' + } + } + SOAP_TYPE = 'ns1:ParcelType' + + def __init__(self, client, weight=None, length=None, width=None, height=None, + contents=None, value=None, tracking_number=None): + super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) + self.client = client + self.weight = weight + self.length = length + self.width = width + self.height = height + self.contents = contents + self.value = value + self.tracking_number = tracking_number + + @classmethod + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ + return cls( + client=client, + weight=soap_dict.get('Weight', None), + length=soap_dict.get('Length', None), + width=soap_dict.get('Width', None), + height=soap_dict.get('Height', None), + contents=soap_dict.get('Contents', None), + value=soap_dict.get('Value', None), + tracking_number=soap_dict.get('TrackingNumber', None) + ) + + +class PaymentMethod(Entity): + + SOAP_MAP = { + 'PaymentMethodID': { + 'property': 'payment_method_id', + 'type': 'integer' + }, + 'Type': { + 'property': 'payment_method_type', + 'type': 'string' + }, + 'Description': { + 'property': 'description', + 'type': 'string' + } + } + SOAP_TYPE = 'ns1:PaymentMethodType' + + def __init__(self, client, payment_method_id=None, payment_method_type=None, description=None): + super().__init__(self.SOAP_TYPE, client.account_client, self.SOAP_MAP) + self.payment_method_id = payment_method_id + self.payment_method_type = payment_method_type + self.description = description + + @classmethod + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ + return cls( + client=client, + payment_method_id=soap_dict.get('PaymentMethodID', None), + payment_method_type=soap_dict.get('Type', None), + description=soap_dict.get('Description', None) + ) + + +class Recipient(object): + # todo: dry out + def __init__(self, client, name=None, telephone=None, email=None, recipient_address=None): + self.shipping_client = client.shipping_client + self.type_name = 'ns1:RecipientAddressType' + self.name = name + self.telephone = telephone + self.email = email + self.recipient_address = recipient_address + + @classmethod + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ + return cls( + client=client, + name=soap_dict.get('RecipientName', None), + telephone=soap_dict.get('RecipientTelephone', None), + email=soap_dict.get('RecipientEmail', None), + recipient_address=Address.from_dict( + client, + client.shipping_client.dict(soap_dict.get('RecipientAddress', None)) + ) + ) + + def to_soap_object(self): + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.shipping_client.factory.create(self.type_name) + suds_object.RecipientName = self.name + suds_object.RecipientTelephone = self.telephone + suds_object.RecipientEmail = self.email + suds_object.RecipientAddress = self.recipient_address.to_soap_object() + return suds_object + + +class Sender(object): + # todo: dry out + def __init__(self, client, name=None, telephone=None, email=None, sender_address=None, address_id=None): + self.client = client + self.type_name = 'ns1:SenderAddressType' + self.name = name + self.telephone = telephone + self.email = email + self.address_id = address_id + self.sender_address = sender_address + + @classmethod + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ + return cls( + client=client, + name=soap_dict.get('SenderName', None), + telephone=soap_dict.get('SenderTelephone', None), + email=soap_dict.get('SenderEmail', None), + address_id=soap_dict.get('SenderAddressID'), + sender_address=Address.from_dict( + client, + client.shipping_client.dict(soap_dict.get('SenderAddress', None)) + ) + ) + + def to_soap_object(self): + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.client.shipping_client.factory.create(self.type_name) + suds_object.SenderName = self.name + suds_object.SenderTelephone = self.telephone + suds_object.SenderEmail = self.email + if self.address_id: + suds_object.SenderAddressID = self.address_id + else: + suds_object.SenderAddress = self.sender_address.to_soap_object() + return suds_object + + +class Service(object): + # todo: dry out + def __init__(self, client, service_id, service_format, name, cost, courier): + self.client = client + self.type_name = 'ns1:ServiceType' + self.service_id = service_id + self.format = service_format + self.name = name + self.cost = cost + self.courier = courier + + @classmethod + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ + return cls( + client=client, + service_id=soap_dict.get('ServiceID', None), + service_format=soap_dict.get('Format', None), + name=soap_dict.get('Name', None), + cost=soap_dict.get('Cost', None), + courier=Courier.from_dict( + client, + client.shipping_client.dict(soap_dict.get('Courier', None)) + ) + ) + + def to_soap_object(self): + """ + Creates a SOAP client object representation of this entity. + """ + suds_object = self.client.factory.shipping_client.create(self.type_name) + suds_object.CourierID = self.service_id + suds_object.CourierName = self.name + return suds_object + + +class ShipmentRequest(Entity): + SOAP_MAP = { + 'ServiceID': { + 'property': 'service_id', + 'type': 'string', + }, + 'Parcels': { + 'property': 'parcels', + 'type': 'entityArray', + 'soap_type': 'ns1:ArrayOfParcelType', + }, + 'ClientReference': { + 'property': 'client_reference', + 'type': 'string', + }, + 'CollectionDate': { + 'property': 'collection_date', + 'type': 'entity', + }, + 'RecipientAddress': { + 'property': 'recipient_address', + 'type': 'entity', + }, + 'SenderAddress': { + 'property': 'sender_address', + 'type': 'entity', + }, + 'FollowShipment': { + 'property': 'follow_shipment', + 'type': 'boolean', + } + } + + SOAP_TYPE = 'ns1:ShipmentRequestType' + + def __init__(self, client, service_id=None, parcels=None, client_reference=None, collection_date=None, + sender_address=None, recipient_address=None, follow_shipment=None): + super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) + self._despatchbay_client = client + self.service_id = service_id + self.parcels = parcels + self.client_reference = client_reference + self._collection_date = self.validate_collection_date_object(collection_date) + self.sender_address = sender_address + self.recipient_address = recipient_address + self.follow_shipment = follow_shipment + + def validate_collection_date_object(self, collection_date): + if isinstance(collection_date, str): + return CollectionDate(self._despatchbay_client, date=collection_date) + else: + return collection_date + + @property + def collection_date(self): + return self._collection_date + + @collection_date.setter + def collection_date(self, collection_date): + self._collection_date = self.validate_collection_date_object(collection_date) + + +class ShipmentReturn(object): + # todo: dry out or move + def __init__(self, client, shipment_id=None, shipment_document_id=None, collection_id=None, + service_id=None, parcels=None, client_reference=None, recipient_address=None, + is_followed=None, is_printed=None, is_despatched=None, is_delivered=None, + is_cancelled=None, labels_url=None): + self.despatchbay_client = client + self.shipping_client = client.shipping_client + self.type_name = 'ns1:ShipmentReturnType' + self.shipment_id = shipment_id + self.shipment_document_id = shipment_document_id + self.collection_id = collection_id + self.service_id = service_id + self.parcels = parcels + self.client_reference = client_reference + self.recipient_address = recipient_address + self.is_followed = is_followed + self.is_printed = is_printed + self.is_despatched = is_despatched + self.is_delivered = is_delivered + self.is_cancelled = is_cancelled + self.labels_url = labels_url + + @classmethod + def from_dict(cls, client, soap_dict): + """ + Alternative constructor, builds entity object from a dictionary representation of + a SOAP response created by the SOAP client. + """ + parcel_array = [] + for parcel_item in soap_dict.get('Parcels'): + parcel_array.append( + Parcel.from_dict( + client, + client.shipping_client.dict(parcel_item) + ) + ) + return cls( + client=client, + shipment_id=soap_dict.get('ShipmentID'), + shipment_document_id=soap_dict.get('ShipmentDocumentID'), + collection_id=soap_dict.get('CollectionID'), + service_id=soap_dict.get('ServiceID'), + parcels=parcel_array, + client_reference=soap_dict.get('ClientReference'), + recipient_address=Recipient.from_dict( + client, + client.shipping_client.dict(soap_dict.get('RecipientAddress', None)) + ), + is_followed=soap_dict.get('IsFollowed'), + is_printed=soap_dict.get('IsPrinted'), + is_despatched=soap_dict.get('IsDespatched'), + is_delivered=soap_dict.get('IsDelivered'), + is_cancelled=soap_dict.get('IsCancelled'), + labels_url=soap_dict.get('LabelsURL', None) + ) + + def cancel(self): + """ + Makes a CancelShipment request through the Despatch Bay API client. + """ + cancel_return = self.despatchbay_client.cancel_shipment(self.shipment_id) + if cancel_return: + self.is_cancelled = True + return cancel_return + + def get_labels(self, **kwargs): + """ + Fetches label pdf through the Despatch Bay API client. + """ + return self.despatchbay_client.fetch_shipment_labels(self.shipment_document_id, **kwargs) diff --git a/despatchbay/entities/__init__.py b/despatchbay/entities/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/despatchbay/entities/account.py b/despatchbay/entities/account.py deleted file mode 100644 index 0c40d1c..0000000 --- a/despatchbay/entities/account.py +++ /dev/null @@ -1,46 +0,0 @@ -from despatchbay.entities import account_balance -from entity import Entity - - -class Account(Entity): - - # todo: entity class - SOAP_MAP = { - 'AccountID': { - 'property': 'account_id', - 'type': 'integer' - }, - 'AccountName': { - 'property': 'name', - 'type': 'string' - }, - 'AccountBalance': { - 'property': 'balance', - 'type': 'entity', - # 'entityClass': 'DespatchBay\Entity\AccountBalance' - } - } - - SOAP_TYPE = 'ns1:AccountType' - - def __init__(self, client, account_id=None, name=None, balance=None): - super().__init__(self.SOAP_TYPE, client.account_client, self.SOAP_MAP) - self.account_id = account_id - self.name = name - self.balance = balance - - @classmethod - def from_dict(cls, client, soap_dict): - """ - Alternative constructor, builds entity object from a dictionary representation of - a SOAP response created by the SOAP client. - """ - return cls( - client=client, - account_id=soap_dict.get('AccountID', None), - name=soap_dict.get('AccountName', None), - balance=account_balance.AccountBalance.from_dict( - client, - client.account_client.dict(soap_dict.get('AccountBalance', None)) - ) - ) diff --git a/despatchbay/entities/account_balance.py b/despatchbay/entities/account_balance.py deleted file mode 100644 index 54bb67d..0000000 --- a/despatchbay/entities/account_balance.py +++ /dev/null @@ -1,33 +0,0 @@ -from entity import Entity - - -class AccountBalance(Entity): - SOAP_MAP = { - 'Balance': { - 'property': 'balance', - 'type': 'float' - }, - 'AvailableBalance': { - 'property': 'available', - 'type': 'float' - } - } - - SOAP_TYPE = 'ns1:AccountBalanceType' - - def __init__(self, client, balance=None, available=None): - super().__init__(self.SOAP_TYPE, client.account_client, self.SOAP_MAP) - self.balance = balance - self.available = available - - @classmethod - def from_dict(cls, client, soap_dict): - """ - Alternative constructor, builds entity object from a dictionary representation of - a SOAP response created by the SOAP client. - """ - return cls( - client=client, - balance=soap_dict.get('Balance', None), - available=soap_dict.get('AvailableBalance', None) - ) diff --git a/despatchbay/entities/address.py b/despatchbay/entities/address.py deleted file mode 100644 index 5852bc6..0000000 --- a/despatchbay/entities/address.py +++ /dev/null @@ -1,66 +0,0 @@ -from entity import Entity - - -class Address(Entity): - - SOAP_MAP = { - 'CompanyName': { - 'property': 'company_name', - 'type': 'string', - }, - 'Street': { - 'property': 'street', - 'type': 'string', - }, - 'Locality': { - 'property': 'locality', - 'type': 'string', - }, - 'TownCity': { - 'property': 'town_city', - 'type': 'string', - }, - 'County': { - 'property': 'county', - 'type': 'string', - }, - 'PostalCode': { - 'property': 'postal_code', - 'type': 'string', - }, - 'CountryCode': { - 'property': 'country_code', - 'type': 'string', - } - } - - SOAP_TYPE = 'ns1:AddressType' - - def __init__(self, client, company_name=None, street=None, locality=None, town_city=None, county=None, - postal_code=None, country_code=None): - super().__init__(self.SOAP_TYPE, client.addressing_client, self.SOAP_MAP) - self.company_name = company_name - self.street = street - self.locality = locality - self.town_city = town_city - self.county = county - self.postal_code = postal_code - self.country_code = country_code - - @classmethod - def from_dict(cls, client, soap_dict): - """ - Alternative initialiser, builds entity object from a dictionary representation of - a SOAP response created by the SOAP client. - """ - return cls( - client=client, - company_name=soap_dict.get('CompanyName', None), - street=soap_dict.get('Street', None), - locality=soap_dict.get('Locality', None), - town_city=soap_dict.get('TownCity', None), - county=soap_dict.get('County', None), - postal_code=soap_dict.get('PostalCode', None), - country_code=soap_dict.get('CountryCode', None) - ) - diff --git a/despatchbay/entities/address_key.py b/despatchbay/entities/address_key.py deleted file mode 100644 index 9a53da4..0000000 --- a/despatchbay/entities/address_key.py +++ /dev/null @@ -1,32 +0,0 @@ -from entity import Entity - - -class AddressKey(Entity): - SOAP_MAP = { - 'Key': { - 'property': 'key', - 'type': 'string', - }, - 'Address': { - 'property': 'address', - 'type': 'string', - } - } - SOAP_TYPE = 'ns1:AddressKeyType' - - def __init__(self, client, key, address): - super().__init__(self.SOAP_TYPE, client, self.SOAP_MAP) - self.key = key - self.address = address - - @classmethod - def from_dict(cls, client, soap_dict): - """ - Alternative constructor, builds entity object from a dictionary representation of - a SOAP response created by the SOAP client. - """ - return cls( - client=client, - key=soap_dict.get('Key', None), - address=soap_dict.get('Address', None) - ) diff --git a/despatchbay/entities/automatic_topup_settings.py b/despatchbay/entities/automatic_topup_settings.py deleted file mode 100644 index 88005fd..0000000 --- a/despatchbay/entities/automatic_topup_settings.py +++ /dev/null @@ -1,38 +0,0 @@ -from entity import Entity - - -class AutomaticTopupSettings(Entity): - SOAP_MAP = { - "MinimumBalance": { - "property": "minimum_balance", - "type": "float" - }, - "TopupAmount": { - "property": "topup_amount", - "type": "float" - }, - "PaymentMethodID": { - "property": "payment_method_id", - "type": "string" - } - } - SOAP_TYPE = 'ns1:AutomaticTopupsSettingsRequestType' - - def __init__(self, client, minimum_balance=None, topup_amount=None, payment_method_id=None): - super().__init__(self.SOAP_TYPE, client.account_client, self.SOAP_MAP) - self.minimum_balance = minimum_balance - self.topup_amount = topup_amount - self.payment_method_id = payment_method_id - - @classmethod - def from_dict(cls, client, soap_dict): - """ - Alternative constructor, builds entity object from a dictionary representation of - a SOAP response created by the SOAP client. - """ - return cls( - client=client, - minimum_balance=soap_dict.get('MinimumBalance', None), - topup_amount=soap_dict.get('TopupAmount', None), - payment_method_id=soap_dict.get('PaymentMethodID', None) - ) diff --git a/despatchbay/entities/collection.py b/despatchbay/entities/collection.py deleted file mode 100644 index 35ff597..0000000 --- a/despatchbay/entities/collection.py +++ /dev/null @@ -1,81 +0,0 @@ -from despatchbay.entities import sender, courier, collection_date -from entity import Entity - - -class Collection(Entity): - - SOAP_MAP = { - "CollectionID": { - "property": "collection_id", - "type": "string" - }, - "CollectionDocumentID": { - "property": "document_id", - "type": "string" - }, - "CollectionType": { - "property": "collection_type", - "type": "string" - }, - "CollectionDate": { - "property": "date", - "type": "string" - }, - "SenderAddress": { - "property": "sender_address", - "type": "string" - }, - "Courier": { - "property": "collection_courier", - "type": "string" - }, - "LabelsURL": { - "property": "labels_url", - "type": "string" - }, - "Manifest": { - "property": "manifest_url", - "type": "string" - } - } - - SOAP_TYPE = 'ns1:CollectionReturnType' - - def __init__(self, client, collection_id=None, document_id=None, collection_type=None, date=None, - sender_address=None, collection_courier=None, labels_url=None, manifest_url=None): - super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) - self.collection_id = collection_id - self.document_id = document_id - self.collection_type = collection_type - self.date = date - self.sender_address = sender_address - self.collection_courier = collection_courier - self.labels_url = labels_url - self.manifest_url = manifest_url - - @classmethod - def from_dict(cls, client, soap_dict): - """ - Alternative constructor, builds entity object from a dictionary representation of - a SOAP response created by the SOAP client. - """ - return cls( - client=client, - collection_id=soap_dict.get('CollectionID'), - document_id=soap_dict.get('CollectionDocumentID'), - collection_type=soap_dict.get('CollectionType'), - date=collection_date.CollectionDate.from_dict( - client, - client.dict(soap_dict.get('CollectionDate')) - ), - sender_address=sender.Sender.from_dict( - client, - client.dict(soap_dict.get('SenderAddress', None)) - ), - collection_courier=courier.Courier.from_dict( - client, - client.dict(soap_dict.get('Courier', None)) - ), - labels_url=soap_dict.get('LabelsURL', None), - manifest_url=soap_dict.get('Manifest', None) - ) diff --git a/despatchbay/entities/collection_date.py b/despatchbay/entities/collection_date.py deleted file mode 100644 index 6516937..0000000 --- a/despatchbay/entities/collection_date.py +++ /dev/null @@ -1,27 +0,0 @@ -from entity import Entity - - -class CollectionDate(Entity): - SOAP_MAP = { - "CollectionDate": { - "property": "date", - "type": "string" - } - } - - SOAP_TYPE = 'ns1:CollectionDateType' - - def __init__(self, client, date=None): - super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) - self.date = date - - @classmethod - def from_dict(cls, client, soap_dict): - """ - Alternative constructor, builds entity object from a dictionary representation of - a SOAP response created by the SOAP client. - """ - return cls( - client=client, - date=soap_dict.get('CollectionDate', None) - ) diff --git a/despatchbay/entities/courier.py b/despatchbay/entities/courier.py deleted file mode 100644 index 0ca5a67..0000000 --- a/despatchbay/entities/courier.py +++ /dev/null @@ -1,33 +0,0 @@ -from entity import Entity - - -class Courier(Entity): - - SOAP_MAP = { - 'CourierID': { - 'property': 'courier_id', - 'type': 'integer' - }, - 'CourierName': { - 'property': 'name', - 'type': 'string' - } - } - SOAP_TYPE = 'ns1:CourierType' - - def __init__(self, client, courier_id=None, name=None): - super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) - self.courier_id = courier_id - self.name = name - - @classmethod - def from_dict(cls, client, soap_dict): - """ - Alternative constructor, builds entity object from a dictionary representation of - a SOAP response created by the SOAP client. - """ - return cls( - client=client, - courier_id=soap_dict.get('CourierID', None), - name=soap_dict.get('CourierName', None) - ) diff --git a/despatchbay/entities/document.py b/despatchbay/entities/document.py deleted file mode 100644 index 95dc309..0000000 --- a/despatchbay/entities/document.py +++ /dev/null @@ -1,37 +0,0 @@ -import base64 - - -class Pdf(object): - def __init__(self, data): - if self.is_pdf(data): - self.data = data - else: - raise TypeError("File returned from api is not a valid PDF.") - - @staticmethod - def is_pdf(data): - """ - Performs a rudimentary check to see if the data APPEARS to be a - valid POF file. - """ - return data[0:4].decode() == '%PDF' - - def get_raw(self): - """ - Returns the raw data used to create the entity. - """ - return self.data - - def get_base64(self): - """ - Base 64 encodes the PDF data before returning it. - """ - return base64.b64decode(self.data) - - def download(self, path): - """ - Saves the PDF to the specified location. - """ - with open(path, 'wb') as pdf_file: - pdf_file.write(self.data) - diff --git a/despatchbay/entities/parcel.py b/despatchbay/entities/parcel.py deleted file mode 100644 index c06371a..0000000 --- a/despatchbay/entities/parcel.py +++ /dev/null @@ -1,65 +0,0 @@ -from entity import Entity - - -class Parcel(Entity): - - SOAP_MAP = { - 'Weight': { - 'property': 'weight', - 'type': 'float' - }, - 'Length': { - 'property': 'length', - 'type': 'float' - }, - 'Width': { - 'property': 'width', - 'type': 'float' - }, - 'Height': { - 'property': 'height', - 'type': 'float' - }, - 'Contents': { - 'property': 'contents', - 'type': 'string' - }, - 'Value': { - 'property': 'value', - 'type': 'float' - }, - 'TrackingNumber': { - 'property': 'tracking_number', - 'type': 'string' - } - } - SOAP_TYPE = 'ns1:ParcelType' - - def __init__(self, client, weight=None, length=None, width=None, height=None, - contents=None, value=None, tracking_number=None): - super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) - self.client = client - self.weight = weight - self.length = length - self.width = width - self.height = height - self.contents = contents - self.value = value - self.tracking_number = tracking_number - - @classmethod - def from_dict(cls, client, soap_dict): - """ - Alternative constructor, builds entity object from a dictionary representation of - a SOAP response created by the SOAP client. - """ - return cls( - client=client, - weight=soap_dict.get('Weight', None), - length=soap_dict.get('Length', None), - width=soap_dict.get('Width', None), - height=soap_dict.get('Height', None), - contents=soap_dict.get('Contents', None), - value=soap_dict.get('Value', None), - tracking_number=soap_dict.get('TrackingNumber', None) - ) diff --git a/despatchbay/entities/payment_method.py b/despatchbay/entities/payment_method.py deleted file mode 100644 index 028266c..0000000 --- a/despatchbay/entities/payment_method.py +++ /dev/null @@ -1,39 +0,0 @@ -from entity import Entity - - -class PaymentMethod(Entity): - - SOAP_MAP = { - 'PaymentMethodID': { - 'property': 'payment_method_id', - 'type': 'integer' - }, - 'Type': { - 'property': 'payment_method_type', - 'type': 'string' - }, - 'Description': { - 'property': 'description', - 'type': 'string' - } - } - SOAP_TYPE = 'ns1:PaymentMethodType' - - def __init__(self, client, payment_method_id=None, payment_method_type=None, description=None): - super().__init__(self.SOAP_TYPE, client.account_client, self.SOAP_MAP) - self.payment_method_id = payment_method_id - self.payment_method_type = payment_method_type - self.description = description - - @classmethod - def from_dict(cls, client, soap_dict): - """ - Alternative constructor, builds entity object from a dictionary representation of - a SOAP response created by the SOAP client. - """ - return cls( - client=client, - payment_method_id=soap_dict.get('PaymentMethodID', None), - payment_method_type=soap_dict.get('Type', None), - description=soap_dict.get('Description', None) - ) diff --git a/despatchbay/entities/recipient.py b/despatchbay/entities/recipient.py deleted file mode 100644 index a841a5c..0000000 --- a/despatchbay/entities/recipient.py +++ /dev/null @@ -1,39 +0,0 @@ -from despatchbay.entities import address - - -class Recipient(object): - def __init__(self, client, name=None, telephone=None, email=None, recipient_address=None): - self.shipping_client = client.shipping_client - self.type_name = 'ns1:RecipientAddressType' - self.name = name - self.telephone = telephone - self.email = email - self.recipient_address = recipient_address - - @classmethod - def from_dict(cls, client, soap_dict): - """ - Alternative constructor, builds entity object from a dictionary representation of - a SOAP response created by the SOAP client. - """ - return cls( - client=client, - name=soap_dict.get('RecipientName', None), - telephone=soap_dict.get('RecipientTelephone', None), - email=soap_dict.get('RecipientEmail', None), - recipient_address=address.Address.from_dict( - client, - client.shipping_client.dict(soap_dict.get('RecipientAddress', None)) - ) - ) - - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.shipping_client.factory.create(self.type_name) - suds_object.RecipientName = self.name - suds_object.RecipientTelephone = self.telephone - suds_object.RecipientEmail = self.email - suds_object.RecipientAddress = self.recipient_address.to_soap_object() - return suds_object diff --git a/despatchbay/entities/sender.py b/despatchbay/entities/sender.py deleted file mode 100644 index 35aa1ac..0000000 --- a/despatchbay/entities/sender.py +++ /dev/null @@ -1,44 +0,0 @@ -from despatchbay.entities import address - - -class Sender(object): - def __init__(self, client, name=None, telephone=None, email=None, sender_address=None, address_id=None): - self.client = client - self.type_name = 'ns1:SenderAddressType' - self.name = name - self.telephone = telephone - self.email = email - self.address_id = address_id - self.sender_address = sender_address - - @classmethod - def from_dict(cls, client, soap_dict): - """ - Alternative constructor, builds entity object from a dictionary representation of - a SOAP response created by the SOAP client. - """ - return cls( - client=client, - name=soap_dict.get('SenderName', None), - telephone=soap_dict.get('SenderTelephone', None), - email=soap_dict.get('SenderEmail', None), - address_id=soap_dict.get('SenderAddressID'), - sender_address=address.Address.from_dict( - client, - client.shipping_client.dict(soap_dict.get('SenderAddress', None)) - ) - ) - - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.client.shipping_client.factory.create(self.type_name) - suds_object.SenderName = self.name - suds_object.SenderTelephone = self.telephone - suds_object.SenderEmail = self.email - if self.address_id: - suds_object.SenderAddressID = self.address_id - else: - suds_object.SenderAddress = self.sender_address.to_soap_object() - return suds_object diff --git a/despatchbay/entities/service.py b/despatchbay/entities/service.py deleted file mode 100644 index 49ccbf6..0000000 --- a/despatchbay/entities/service.py +++ /dev/null @@ -1,39 +0,0 @@ -from despatchbay.entities import courier - - -class Service(object): - def __init__(self, client, service_id, service_format, name, cost, courier): - self.client = client - self.type_name = 'ns1:ServiceType' - self.service_id = service_id - self.format = service_format - self.name = name - self.cost = cost - self.courier = courier - - @classmethod - def from_dict(cls, client, soap_dict): - """ - Alternative constructor, builds entity object from a dictionary representation of - a SOAP response created by the SOAP client. - """ - return cls( - client=client, - service_id=soap_dict.get('ServiceID', None), - service_format=soap_dict.get('Format', None), - name=soap_dict.get('Name', None), - cost=soap_dict.get('Cost', None), - courier=courier.Courier.from_dict( - client, - client.shipping_client.dict(soap_dict.get('Courier', None)) - ) - ) - - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.client.factory.shipping_client.create(self.type_name) - suds_object.CourierID = self.service_id - suds_object.CourierName = self.name - return suds_object diff --git a/despatchbay/entities/shipment_request.py b/despatchbay/entities/shipment_request.py deleted file mode 100644 index c51764a..0000000 --- a/despatchbay/entities/shipment_request.py +++ /dev/null @@ -1,64 +0,0 @@ -from despatchbay.entities.collection_date import CollectionDate -from entity import Entity - - -class ShipmentRequest(Entity): - SOAP_MAP = { - 'ServiceID': { - 'property': 'service_id', - 'type': 'string', - }, - 'Parcels': { - 'property': 'parcels', - 'type': 'entityArray', - 'soap_type': 'ns1:ArrayOfParcelType', - }, - 'ClientReference': { - 'property': 'client_reference', - 'type': 'string', - }, - 'CollectionDate': { - 'property': 'collection_date', - 'type': 'entity', - }, - 'RecipientAddress': { - 'property': 'recipient_address', - 'type': 'entity', - }, - 'SenderAddress': { - 'property': 'sender_address', - 'type': 'entity', - }, - 'FollowShipment': { - 'property': 'follow_shipment', - 'type': 'boolean', - } - } - - SOAP_TYPE = 'ns1:ShipmentRequestType' - - def __init__(self, client, service_id=None, parcels=None, client_reference=None, collection_date=None, - sender_address=None, recipient_address=None, follow_shipment=None): - super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) - self._despatchbay_client = client - self.service_id = service_id - self.parcels = parcels - self.client_reference = client_reference - self._collection_date = self.validate_collection_date_object(collection_date) - self.sender_address = sender_address - self.recipient_address = recipient_address - self.follow_shipment = follow_shipment - - def validate_collection_date_object(self, collection_date): - if isinstance(collection_date, str): - return CollectionDate(self._despatchbay_client, date=collection_date) - else: - return collection_date - - @property - def collection_date(self): - return self._collection_date - - @collection_date.setter - def collection_date(self, collection_date): - self._collection_date = self.validate_collection_date_object(collection_date) diff --git a/despatchbay/entities/shipment_return.py b/despatchbay/entities/shipment_return.py deleted file mode 100644 index c5f83c2..0000000 --- a/despatchbay/entities/shipment_return.py +++ /dev/null @@ -1,74 +0,0 @@ -from despatchbay.entities import recipient -from despatchbay.entities import parcel - - -class ShipmentReturn(object): - def __init__(self, client, shipment_id=None, shipment_document_id=None, collection_id=None, - service_id=None, parcels=None, client_reference=None, recipient_address=None, - is_followed=None, is_printed=None, is_despatched=None, is_delivered=None, - is_cancelled=None, labels_url=None): - self.despatchbay_client = client - self.shipping_client = client.shipping_client - self.type_name = 'ns1:ShipmentReturnType' - self.shipment_id = shipment_id - self.shipment_document_id = shipment_document_id - self.collection_id = collection_id - self.service_id = service_id - self.parcels = parcels - self.client_reference = client_reference - self.recipient_address = recipient_address - self.is_followed = is_followed - self.is_printed = is_printed - self.is_despatched = is_despatched - self.is_delivered = is_delivered - self.is_cancelled = is_cancelled - self.labels_url = labels_url - - @classmethod - def from_dict(cls, client, soap_dict): - """ - Alternative constructor, builds entity object from a dictionary representation of - a SOAP response created by the SOAP client. - """ - parcel_array = [] - for parcel_item in soap_dict.get('Parcels'): - parcel_array.append( - parcel.Parcel.from_dict( - client, - client.shipping_client.dict(parcel_item) - ) - ) - return cls( - client=client, - shipment_id=soap_dict.get('ShipmentID'), - shipment_document_id=soap_dict.get('ShipmentDocumentID'), - collection_id=soap_dict.get('CollectionID'), - service_id=soap_dict.get('ServiceID'), - parcels=parcel_array, - client_reference=soap_dict.get('ClientReference'), - recipient_address=recipient.Recipient.from_dict( - client, - client.shipping_client.dict(soap_dict.get('RecipientAddress', None)) - ), - is_followed=soap_dict.get('IsFollowed'), - is_printed=soap_dict.get('IsPrinted'), - is_despatched=soap_dict.get('IsDespatched'), - is_delivered=soap_dict.get('IsDelivered'), - is_cancelled=soap_dict.get('IsCancelled'), - labels_url=soap_dict.get('LabelsURL', None) - ) - - def cancel(self): - """ - Makes a CancelShipment request through the Despatch Bay API client. - """ - cancel_return = self.despatchbay_client.cancel_shipment(self.shipment_id) - if cancel_return: - self.is_cancelled = True - return cancel_return - - def get_labels(self, **kwargs): - """ - Fetches label pdf through the Despatch Bay API client. - """ - return self.despatchbay_client.fetch_shipment_labels(self.shipment_document_id, **kwargs) diff --git a/despatchbay/library/exception.py b/despatchbay/exceptions.py similarity index 100% rename from despatchbay/library/exception.py rename to despatchbay/exceptions.py diff --git a/despatchbay/library/__init__.py b/despatchbay/library/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/despatchbay/library/entity.py b/despatchbay/library/entity.py deleted file mode 100644 index 706a8fd..0000000 --- a/despatchbay/library/entity.py +++ /dev/null @@ -1,37 +0,0 @@ -class Entity(object): - - def __init__(self, soap_type, soap_client, soap_map): - self.soap_type = soap_type - self.soap_client = soap_client - self.soap_map = soap_map - - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.soap_client.factory.create(self.soap_type) - for soap_property in self.soap_map: - if self.soap_map[soap_property]['type'] == 'entity': - setattr( - suds_object, - soap_property, - getattr( - self, - self.soap_map[soap_property]['property']).to_soap_object() - ) - elif self.soap_map[soap_property]['type'] == 'entityArray': - entity_list = [] - for entity in getattr(self, self.soap_map[soap_property]['property']): - entity_list.append(entity.to_soap_object()) - soap_array = self.soap_client.factory.create(self.soap_map[soap_property]['soap_type']) - soap_array.item = entity_list - soap_array._arrayType = 'urn:ArrayType[]' - setattr(suds_object, soap_property, soap_array) - else: - setattr( - suds_object, soap_property, getattr( - self, - self.soap_map[soap_property]['property'] - ) - ) - return suds_object diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..a5a45ec --- /dev/null +++ b/setup.py @@ -0,0 +1,11 @@ +from setuptools import setup + +setup(name='despatchbay', + version='0.1', + description='Despatchbay SDK', + url='', + author='', + author_email='', + license='MIT', + packages=['despatchbay'], + zip_safe=False) From e50718adaee5879198f6cb4e6b4fdf075069f34a Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Fri, 26 Apr 2019 09:58:20 +0100 Subject: [PATCH 22/41] Fixed sdk imports --- despatchbay/despatchbay_sdk.py | 46 +++++++++---------- {despatchbay/tests => tests}/__init__.py | 0 {despatchbay/tests => tests}/pdf_test.py | 0 {despatchbay/tests => tests}/test_accounts.py | 0 .../tests => tests}/test_addresses.py | 0 {despatchbay/tests => tests}/test_shipping.py | 0 6 files changed, 22 insertions(+), 24 deletions(-) rename {despatchbay/tests => tests}/__init__.py (100%) rename {despatchbay/tests => tests}/pdf_test.py (100%) rename {despatchbay/tests => tests}/test_accounts.py (100%) rename {despatchbay/tests => tests}/test_addresses.py (100%) rename {despatchbay/tests => tests}/test_shipping.py (100%) diff --git a/despatchbay/despatchbay_sdk.py b/despatchbay/despatchbay_sdk.py index 74de0bc..99eb536 100644 --- a/despatchbay/despatchbay_sdk.py +++ b/despatchbay/despatchbay_sdk.py @@ -3,9 +3,7 @@ import suds from despatchbay.exceptions import AuthorizationException,\ ApiException, ConnectionException, RateLimitException -from despatchbay.entities import Parcel, Address, Recipient, Sender, ShipmentRequest, Account, AccountBalance,\ - AddressKey, Service, Collection, ShipmentReturn, PaymentMethod, AutomaticTopupSettings, CollectionDate -from despatchbay.documents_client import DocumentsClient +from . import entities, documents_client def handle_suds_fault(error): @@ -49,7 +47,7 @@ def __init__(self, api_user, api_key, api_domain='api.despatchbay.com', api_vers shipping_url, transport=self.create_transport(api_user, api_key)) self.tracking_client = Client( tracking_url, transport=self.create_transport(api_user, api_key)) - self.pdf_client = DocumentsClient(api_url=documents_url) + self.pdf_client = documents_client.DocumentsClient(api_url=documents_url) @staticmethod def create_transport(username, password): @@ -61,31 +59,31 @@ def parcel(self, **kwargs): """ Creates a dbp parcel entity """ - return Parcel(self, **kwargs) + return entities.Parcel(self, **kwargs) def address(self, **kwargs): """ Creates a dbp address entity """ - return Address(self, **kwargs) + return entities.Address(self, **kwargs) def recipient(self, **kwargs): """ Creates a dbp recipient address entity """ - return Recipient(self, **kwargs) + return entities.Recipient(self, **kwargs) def sender(self, **kwargs): """ Creates a dbp sender address entity """ - return Sender(self, **kwargs) + return entities.Sender(self, **kwargs) def shipment_request(self, **kwargs): """ Creates a dbp shipment entity """ - return ShipmentRequest(self, **kwargs) + return entities.ShipmentRequest(self, **kwargs) # Account Services @@ -93,7 +91,7 @@ def shipment_request(self, **kwargs): def get_account(self): """Calls GetAccount from the Despatch Bay Account Service.""" account_dict = self.account_client.dict(self.account_client.service.GetAccount()) - return Account.from_dict( + return entities.Account.from_dict( self, account_dict ) @@ -104,7 +102,7 @@ def get_account_balance(self): Calls GetBalance from the Despatch Bay Account Service. """ balance_dict = self.account_client.dict(self.account_client.service.GetAccountBalance()) - return AccountBalance.from_dict( + return entities.AccountBalance.from_dict( self, balance_dict ) @@ -117,7 +115,7 @@ def get_sender_addresses(self): sender_addresses_dict_list = [] for sender_address in self.account_client.service.GetSenderAddresses(): sender_address_dict = self.account_client.dict(sender_address) - sender_addresses_dict_list.append(Sender.from_dict( + sender_addresses_dict_list.append(entities.Sender.from_dict( self, sender_address_dict)) return sender_addresses_dict_list @@ -130,7 +128,7 @@ def get_services(self): service_list = [] for account_service in self.account_client.service.GetServices(): service_list.append( - Service.from_dict( + entities.Service.from_dict( self, self.account_client.dict(account_service) )) @@ -144,7 +142,7 @@ def get_payment_methods(self): payment_methods = [] for payment_method in self.account_client.service.GetPaymentMethods(): payment_methods.append( - PaymentMethod.from_dict( + entities.PaymentMethod.from_dict( self, self.account_client.dict(payment_method) ) @@ -160,7 +158,7 @@ def enable_automatic_topups(self, minimum_balance=None, topup_amount=None, Passing an automatic_topup_settings object takes priority over using individual arguments. """ if not automatic_topup_settings_object: - automatic_topup_settings_object = AutomaticTopupSettings( + automatic_topup_settings_object = entities.AutomaticTopupSettings( self, minimum_balance, topup_amount, payment_method_id) return self.account_client.service.EnableAutomaticTopups( automatic_topup_settings_object.to_soap_object()) @@ -183,7 +181,7 @@ def find_address(self, postcode, property_string): self.addressing_client.service.FindAddress( postcode, property_string )) - return Address.from_dict( + return entities.Address.from_dict( self, found_address_dict ) @@ -196,7 +194,7 @@ def get_address_by_key(self, key): found_address_dict = self.addressing_client.dict( self.addressing_client.service.GetAddressByKey(key) ) - return Address.from_dict( + return entities.Address.from_dict( self, found_address_dict ) @@ -209,7 +207,7 @@ def get_address_keys_by_postcode(self, postcode): address_keys_dict_list = [] for soap_address_key in self.addressing_client.service.GetAddressKeysByPostcode(postcode): address_key_dict = self.account_client.dict(soap_address_key) - address_keys_dict_list.append(AddressKey.from_dict( + address_keys_dict_list.append(entities.AddressKey.from_dict( self, address_key_dict)) return address_keys_dict_list @@ -225,7 +223,7 @@ def get_available_services(self, shipment_request): for available_service in self.shipping_client.service.GetAvailableServices( shipment_request.to_soap_object()): available_service_dict = self.shipping_client.dict(available_service) - available_service_dict_list.append(Service.from_dict( + available_service_dict_list.append(entities.Service.from_dict( self, available_service_dict)) return available_service_dict_list @@ -240,7 +238,7 @@ def get_available_collection_dates(self, sender_address, courier_id): available_collection_dates_list = [] for collection_date in available_collection_dates_response: collection_date_dict = self.shipping_client.dict(collection_date) - available_collection_dates_list.append(CollectionDate.from_dict(self, collection_date_dict)) + available_collection_dates_list.append(entities.CollectionDate.from_dict(self, collection_date_dict)) return available_collection_dates_list @try_except @@ -250,7 +248,7 @@ def get_collection(self, collection_id): """ collection_dict = self.shipping_client.dict( self.shipping_client.service.GetCollection(collection_id)) - return Address.from_dict( + return entities.Address.from_dict( self, collection_dict ) @@ -264,7 +262,7 @@ def get_collections(self): for found_collection in self.shipping_client.service.GetCollections(): collection_dict = self.shipping_client.dict(found_collection) collections_dict_list.append( - Collection.from_dict( + entities.Collection.from_dict( self, collection_dict ) @@ -278,7 +276,7 @@ def get_shipment(self, shipment_id): """ shipment_dict = self.shipping_client.dict( self.shipping_client.service.GetShipment(shipment_id)) - return ShipmentReturn.from_dict( + return entities.ShipmentReturn.from_dict( self, shipment_dict ) @@ -301,7 +299,7 @@ def book_shipments(self, shipment_ids): for booked_shipment in self.shipping_client.service.BookShipments(array_of_shipment_id): booked_shipment_dict = self.shipping_client.dict(booked_shipment) booked_shipments_list.append( - ShipmentReturn.from_dict( + entities.ShipmentReturn.from_dict( self, booked_shipment_dict ) diff --git a/despatchbay/tests/__init__.py b/tests/__init__.py similarity index 100% rename from despatchbay/tests/__init__.py rename to tests/__init__.py diff --git a/despatchbay/tests/pdf_test.py b/tests/pdf_test.py similarity index 100% rename from despatchbay/tests/pdf_test.py rename to tests/pdf_test.py diff --git a/despatchbay/tests/test_accounts.py b/tests/test_accounts.py similarity index 100% rename from despatchbay/tests/test_accounts.py rename to tests/test_accounts.py diff --git a/despatchbay/tests/test_addresses.py b/tests/test_addresses.py similarity index 100% rename from despatchbay/tests/test_addresses.py rename to tests/test_addresses.py diff --git a/despatchbay/tests/test_shipping.py b/tests/test_shipping.py similarity index 100% rename from despatchbay/tests/test_shipping.py rename to tests/test_shipping.py From 57f8c78bc73fff7c355c4ecbc39d3ce25f4f6f0b Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Fri, 26 Apr 2019 11:00:57 +0100 Subject: [PATCH 23/41] More entity refactors --- .../{entities.py => despatchbay_entities.py} | 232 ++++++++++-------- despatchbay/despatchbay_sdk.py | 53 ++-- despatchbay/documents_client.py | 41 +++- 3 files changed, 198 insertions(+), 128 deletions(-) rename despatchbay/{entities.py => despatchbay_entities.py} (86%) diff --git a/despatchbay/entities.py b/despatchbay/despatchbay_entities.py similarity index 86% rename from despatchbay/entities.py rename to despatchbay/despatchbay_entities.py index 75f6e32..6a22bd9 100644 --- a/despatchbay/entities.py +++ b/despatchbay/despatchbay_entities.py @@ -1,6 +1,3 @@ -import base64 - - class Entity(object): def __init__(self, soap_type, soap_client, soap_map): @@ -41,8 +38,6 @@ def to_soap_object(self): class Account(Entity): - - # todo: entity class SOAP_MAP = { 'AccountID': { 'property': 'account_id', @@ -58,7 +53,6 @@ class Account(Entity): # 'entityClass': 'DespatchBay\Entity\AccountBalance' } } - SOAP_TYPE = 'ns1:AccountType' def __init__(self, client, account_id=None, name=None, balance=None): @@ -95,7 +89,6 @@ class AccountBalance(Entity): 'type': 'float' } } - SOAP_TYPE = 'ns1:AccountBalanceType' def __init__(self, client, balance=None, available=None): @@ -148,7 +141,6 @@ class Address(Entity): 'type': 'string', } } - SOAP_TYPE = 'ns1:AddressType' def __init__(self, client, company_name=None, street=None, locality=None, town_city=None, county=None, @@ -284,7 +276,6 @@ class Collection(Entity): "type": "string" } } - SOAP_TYPE = 'ns1:CollectionReturnType' def __init__(self, client, collection_id=None, document_id=None, collection_type=None, date=None, @@ -334,7 +325,6 @@ class CollectionDate(Entity): "type": "string" } } - SOAP_TYPE = 'ns1:CollectionDateType' def __init__(self, client, date=None): @@ -385,42 +375,6 @@ def from_dict(cls, client, soap_dict): ) -class Document(object): - # todo: dry out or move - def __init__(self, data): - if self.is_pdf(data): - self.data = data - else: - raise TypeError("File returned from api is not a valid PDF.") - - @staticmethod - def is_pdf(data): - """ - Performs a rudimentary check to see if the data APPEARS to be a - valid POF file. - """ - return data[0:4].decode() == '%PDF' - - def get_raw(self): - """ - Returns the raw data used to create the entity. - """ - return self.data - - def get_base64(self): - """ - Base 64 encodes the PDF data before returning it. - """ - return base64.b64decode(self.data) - - def download(self, path): - """ - Saves the PDF to the specified location. - """ - with open(path, 'wb') as pdf_file: - pdf_file.write(self.data) - - class Parcel(Entity): SOAP_MAP = { @@ -523,11 +477,29 @@ def from_dict(cls, client, soap_dict): ) -class Recipient(object): - # todo: dry out +class Recipient(Entity): + SOAP_MAP = { + 'RecipientName': { + 'property': 'name', + 'type': 'string', + }, + 'RecipientTelephone': { + 'property': 'telephone', + 'type': 'string', + }, + 'RecipientEmail': { + 'property': 'email', + 'type': 'string', + }, + 'RecipientAddress': { + 'property': 'recipient_address', + 'type': 'entity' + }, + } + SOAP_TYPE = 'ns1:RecipientAddressType' + def __init__(self, client, name=None, telephone=None, email=None, recipient_address=None): - self.shipping_client = client.shipping_client - self.type_name = 'ns1:RecipientAddressType' + super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) self.name = name self.telephone = telephone self.email = email @@ -550,28 +522,40 @@ def from_dict(cls, client, soap_dict): ) ) - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.shipping_client.factory.create(self.type_name) - suds_object.RecipientName = self.name - suds_object.RecipientTelephone = self.telephone - suds_object.RecipientEmail = self.email - suds_object.RecipientAddress = self.recipient_address.to_soap_object() - return suds_object +class Sender(Entity): + # todo handle sender address id + SOAP_MAP = { + 'SenderName': { + 'property': 'name', + 'type': 'string', + }, + 'SenderTelephone': { + 'property': 'telephone', + 'type': 'string', + }, + 'SenderEmail': { + 'property': 'email', + 'type': 'string', + }, + 'SenderAddress': { + 'property': 'sender_address', + 'type': 'entity', + }, + 'SenderAddressID': { + 'property': 'address_id', + 'type': 'integer', + }, + } + SOAP_TYPE = 'ns1:SenderAddressType' -class Sender(object): - # todo: dry out def __init__(self, client, name=None, telephone=None, email=None, sender_address=None, address_id=None): - self.client = client - self.type_name = 'ns1:SenderAddressType' + super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) self.name = name self.telephone = telephone self.email = email - self.address_id = address_id self.sender_address = sender_address + self.address_id = address_id @classmethod def from_dict(cls, client, soap_dict): @@ -584,33 +568,41 @@ def from_dict(cls, client, soap_dict): name=soap_dict.get('SenderName', None), telephone=soap_dict.get('SenderTelephone', None), email=soap_dict.get('SenderEmail', None), - address_id=soap_dict.get('SenderAddressID'), sender_address=Address.from_dict( client, client.shipping_client.dict(soap_dict.get('SenderAddress', None)) - ) + ), + address_id = soap_dict.get('SenderAddressID') ) - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.client.shipping_client.factory.create(self.type_name) - suds_object.SenderName = self.name - suds_object.SenderTelephone = self.telephone - suds_object.SenderEmail = self.email - if self.address_id: - suds_object.SenderAddressID = self.address_id - else: - suds_object.SenderAddress = self.sender_address.to_soap_object() - return suds_object +class Service(Entity): + SOAP_MAP = { + 'ServiceID': { + 'property': 'service_id', + 'type': 'integer' + }, + 'Format': { + 'property': 'service_format', + 'type': 'string' + }, + 'Name': { + 'property': 'name', + 'type': 'string' + }, + 'Cost': { + 'property': 'cost', + 'type': 'currency', + }, + 'Courier': { + 'property': 'courier', + 'type': 'entity', + }, + } + SOAP_TYPE = 'ns1:ServiceType' -class Service(object): - # todo: dry out def __init__(self, client, service_id, service_format, name, cost, courier): - self.client = client - self.type_name = 'ns1:ServiceType' + super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) self.service_id = service_id self.format = service_format self.name = name @@ -635,15 +627,6 @@ def from_dict(cls, client, soap_dict): ) ) - def to_soap_object(self): - """ - Creates a SOAP client object representation of this entity. - """ - suds_object = self.client.factory.shipping_client.create(self.type_name) - suds_object.CourierID = self.service_id - suds_object.CourierName = self.name - return suds_object - class ShipmentRequest(Entity): SOAP_MAP = { @@ -677,7 +660,6 @@ class ShipmentRequest(Entity): 'type': 'boolean', } } - SOAP_TYPE = 'ns1:ShipmentRequestType' def __init__(self, client, service_id=None, parcels=None, client_reference=None, collection_date=None, @@ -707,15 +689,69 @@ def collection_date(self, collection_date): self._collection_date = self.validate_collection_date_object(collection_date) -class ShipmentReturn(object): - # todo: dry out or move +class ShipmentReturn(Entity): + SOAP_MAP = { + 'ShipmentID': { + 'property': 'shipment_id', + 'type': 'string', + }, + 'ShipmentDocumentID': { + 'property': 'shipment_document_id', + 'type': 'string', + }, + 'CollectionID': { + 'property': 'collection_id', + 'type': 'string', + }, + 'ServiceID': { + 'property': 'service_id', + 'type': 'string', + }, + 'Parcels': { + 'property': 'parcels', + 'type': 'entityArray', + }, + 'ClientReference': { + 'property': 'client_reference', + 'type': 'string', + }, + 'RecipientAddress': { + 'property': 'recipient_address', + 'type': 'entity', + }, + 'IsFollowed': { + 'property': 'is_followed', + 'type': 'boolean', + }, + 'IsPrinted': { + 'property': 'is_printed', + 'type': 'boolean', + }, + 'IsDespatched': { + 'property': 'is_despatched', + 'type': 'boolean', + }, + 'IsDelivered': { + 'property': 'is_delivered', + 'type': 'boolean', + }, + 'IsCancelled': { + 'property': 'is_cancelled', + 'type': 'boolean', + }, + 'LabelsURL': { + 'property': 'labels_url', + 'type': 'boolean', + }, + } + SOAP_TYPE = 'ns1:ShipmentReturnType' + def __init__(self, client, shipment_id=None, shipment_document_id=None, collection_id=None, service_id=None, parcels=None, client_reference=None, recipient_address=None, is_followed=None, is_printed=None, is_despatched=None, is_delivered=None, is_cancelled=None, labels_url=None): + super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) self.despatchbay_client = client - self.shipping_client = client.shipping_client - self.type_name = 'ns1:ShipmentReturnType' self.shipment_id = shipment_id self.shipment_document_id = shipment_document_id self.collection_id = collection_id diff --git a/despatchbay/despatchbay_sdk.py b/despatchbay/despatchbay_sdk.py index 99eb536..d2aaafc 100644 --- a/despatchbay/despatchbay_sdk.py +++ b/despatchbay/despatchbay_sdk.py @@ -1,21 +1,19 @@ from suds.client import Client from suds.transport.http import HttpAuthenticated import suds -from despatchbay.exceptions import AuthorizationException,\ - ApiException, ConnectionException, RateLimitException -from . import entities, documents_client +from . import despatchbay_entities, documents_client, exceptions def handle_suds_fault(error): exception_info = error.args[0] if 'Unauthorized' in exception_info: - raise AuthorizationException('Invalid API credentials') from error + raise exceptions.AuthorizationException('Invalid API credentials') from error elif 'Could not connect to host' in exception_info: - raise ConnectionException('Failed to connect to the Despatch Bay API') from error + raise exceptions.ConnectionException('Failed to connect to the Despatch Bay API') from error elif 'Your access rate limit for this service has been exceeded' in exception_info: - raise RateLimitException(exception_info) + raise exceptions.RateLimitException(exception_info) else: - raise ApiException(error) from error + raise exceptions.ApiException(error) from error def try_except(fn): @@ -59,31 +57,31 @@ def parcel(self, **kwargs): """ Creates a dbp parcel entity """ - return entities.Parcel(self, **kwargs) + return despatchbay_entities.Parcel(self, **kwargs) def address(self, **kwargs): """ Creates a dbp address entity """ - return entities.Address(self, **kwargs) + return despatchbay_entities.Address(self, **kwargs) def recipient(self, **kwargs): """ Creates a dbp recipient address entity """ - return entities.Recipient(self, **kwargs) + return despatchbay_entities.Recipient(self, **kwargs) def sender(self, **kwargs): """ Creates a dbp sender address entity """ - return entities.Sender(self, **kwargs) + return despatchbay_entities.Sender(self, **kwargs) def shipment_request(self, **kwargs): """ Creates a dbp shipment entity """ - return entities.ShipmentRequest(self, **kwargs) + return despatchbay_entities.ShipmentRequest(self, **kwargs) # Account Services @@ -91,7 +89,7 @@ def shipment_request(self, **kwargs): def get_account(self): """Calls GetAccount from the Despatch Bay Account Service.""" account_dict = self.account_client.dict(self.account_client.service.GetAccount()) - return entities.Account.from_dict( + return despatchbay_entities.Account.from_dict( self, account_dict ) @@ -102,7 +100,7 @@ def get_account_balance(self): Calls GetBalance from the Despatch Bay Account Service. """ balance_dict = self.account_client.dict(self.account_client.service.GetAccountBalance()) - return entities.AccountBalance.from_dict( + return despatchbay_entities.AccountBalance.from_dict( self, balance_dict ) @@ -115,7 +113,7 @@ def get_sender_addresses(self): sender_addresses_dict_list = [] for sender_address in self.account_client.service.GetSenderAddresses(): sender_address_dict = self.account_client.dict(sender_address) - sender_addresses_dict_list.append(entities.Sender.from_dict( + sender_addresses_dict_list.append(despatchbay_entities.Sender.from_dict( self, sender_address_dict)) return sender_addresses_dict_list @@ -128,7 +126,7 @@ def get_services(self): service_list = [] for account_service in self.account_client.service.GetServices(): service_list.append( - entities.Service.from_dict( + despatchbay_entities.Service.from_dict( self, self.account_client.dict(account_service) )) @@ -142,7 +140,7 @@ def get_payment_methods(self): payment_methods = [] for payment_method in self.account_client.service.GetPaymentMethods(): payment_methods.append( - entities.PaymentMethod.from_dict( + despatchbay_entities.PaymentMethod.from_dict( self, self.account_client.dict(payment_method) ) @@ -158,7 +156,7 @@ def enable_automatic_topups(self, minimum_balance=None, topup_amount=None, Passing an automatic_topup_settings object takes priority over using individual arguments. """ if not automatic_topup_settings_object: - automatic_topup_settings_object = entities.AutomaticTopupSettings( + automatic_topup_settings_object = despatchbay_entities.AutomaticTopupSettings( self, minimum_balance, topup_amount, payment_method_id) return self.account_client.service.EnableAutomaticTopups( automatic_topup_settings_object.to_soap_object()) @@ -181,7 +179,7 @@ def find_address(self, postcode, property_string): self.addressing_client.service.FindAddress( postcode, property_string )) - return entities.Address.from_dict( + return despatchbay_entities.Address.from_dict( self, found_address_dict ) @@ -194,7 +192,7 @@ def get_address_by_key(self, key): found_address_dict = self.addressing_client.dict( self.addressing_client.service.GetAddressByKey(key) ) - return entities.Address.from_dict( + return despatchbay_entities.Address.from_dict( self, found_address_dict ) @@ -207,7 +205,7 @@ def get_address_keys_by_postcode(self, postcode): address_keys_dict_list = [] for soap_address_key in self.addressing_client.service.GetAddressKeysByPostcode(postcode): address_key_dict = self.account_client.dict(soap_address_key) - address_keys_dict_list.append(entities.AddressKey.from_dict( + address_keys_dict_list.append(despatchbay_entities.AddressKey.from_dict( self, address_key_dict)) return address_keys_dict_list @@ -223,7 +221,7 @@ def get_available_services(self, shipment_request): for available_service in self.shipping_client.service.GetAvailableServices( shipment_request.to_soap_object()): available_service_dict = self.shipping_client.dict(available_service) - available_service_dict_list.append(entities.Service.from_dict( + available_service_dict_list.append(despatchbay_entities.Service.from_dict( self, available_service_dict)) return available_service_dict_list @@ -238,7 +236,8 @@ def get_available_collection_dates(self, sender_address, courier_id): available_collection_dates_list = [] for collection_date in available_collection_dates_response: collection_date_dict = self.shipping_client.dict(collection_date) - available_collection_dates_list.append(entities.CollectionDate.from_dict(self, collection_date_dict)) + available_collection_dates_list.append( + despatchbay_entities.CollectionDate.from_dict(self, collection_date_dict)) return available_collection_dates_list @try_except @@ -248,7 +247,7 @@ def get_collection(self, collection_id): """ collection_dict = self.shipping_client.dict( self.shipping_client.service.GetCollection(collection_id)) - return entities.Address.from_dict( + return despatchbay_entities.Address.from_dict( self, collection_dict ) @@ -262,7 +261,7 @@ def get_collections(self): for found_collection in self.shipping_client.service.GetCollections(): collection_dict = self.shipping_client.dict(found_collection) collections_dict_list.append( - entities.Collection.from_dict( + despatchbay_entities.Collection.from_dict( self, collection_dict ) @@ -276,7 +275,7 @@ def get_shipment(self, shipment_id): """ shipment_dict = self.shipping_client.dict( self.shipping_client.service.GetShipment(shipment_id)) - return entities.ShipmentReturn.from_dict( + return despatchbay_entities.ShipmentReturn.from_dict( self, shipment_dict ) @@ -299,7 +298,7 @@ def book_shipments(self, shipment_ids): for booked_shipment in self.shipping_client.service.BookShipments(array_of_shipment_id): booked_shipment_dict = self.shipping_client.dict(booked_shipment) booked_shipments_list.append( - entities.ShipmentReturn.from_dict( + despatchbay_entities.ShipmentReturn.from_dict( self, booked_shipment_dict ) diff --git a/despatchbay/documents_client.py b/despatchbay/documents_client.py index 7fff01a..cc0acd4 100644 --- a/despatchbay/documents_client.py +++ b/despatchbay/documents_client.py @@ -1,6 +1,41 @@ from urllib.parse import urlencode -from despatchbay import exceptions +from . import exceptions import requests +import base64 + +class Document(object): + def __init__(self, data): + if self.is_pdf(data): + self.data = data + else: + raise TypeError("File returned from api is not a valid PDF.") + + @staticmethod + def is_pdf(data): + """ + Performs a rudimentary check to see if the data APPEARS to be a + valid POF file. + """ + return data[0:4].decode() == '%PDF' + + def get_raw(self): + """ + Returns the raw data used to create the entity. + """ + return self.data + + def get_base64(self): + """ + Base 64 encodes the PDF data before returning it. + """ + return base64.b64decode(self.data) + + def download(self, path): + """ + Saves the file to the specified location. + """ + with open(path, 'wb') as document_file: + document_file.write(self.data) class DocumentsClient(object): @@ -47,7 +82,7 @@ def fetch_shipment_labels(self, ship_collect_ids, layout=None, label_format=None label_request_url = label_request_url + '?' + query_string response = requests.get(label_request_url) self.handle_response_code(response.status_code) - return document.Pdf(response.content) + return Document(response.content) def fetch_manifest(self, collection_id): """ @@ -56,4 +91,4 @@ def fetch_manifest(self, collection_id): manifest_request_url = '{}/manifest/{}'.format(self.api_url, collection_id) response = requests.get(manifest_request_url) self.handle_response_code(response.status_code) - return document.Pdf(response.content) + return Document(response.content) From d27c06e02b35486d91ae41605918911f4139e4cb Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Fri, 26 Apr 2019 11:41:47 +0100 Subject: [PATCH 24/41] .gitignore --- .gitignore | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c3d2992..53b258f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,15 @@ # Add any directories, files, or patterns you don't want to be tracked by version control +# Compiled python modules. *.pyc -.idea \ No newline at end of file + +# Setuptools distribution folder. +/dist/ + +# Python egg metadata, regenerated from source files by setuptools. +/*.egg-info + +.idea +*.ipynb* +*.pdf +venv/ +/tests/ From 2fcff609519e04634e0bbc124b604bd7fb2a15a9 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Fri, 26 Apr 2019 11:43:57 +0100 Subject: [PATCH 25/41] Removed dev tests --- tests/__init__.py | 0 tests/pdf_test.py | 7 ------ tests/test_accounts.py | 35 ------------------------------ tests/test_addresses.py | 17 --------------- tests/test_shipping.py | 48 ----------------------------------------- 5 files changed, 107 deletions(-) delete mode 100644 tests/__init__.py delete mode 100644 tests/pdf_test.py delete mode 100644 tests/test_accounts.py delete mode 100644 tests/test_addresses.py delete mode 100644 tests/test_shipping.py diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/pdf_test.py b/tests/pdf_test.py deleted file mode 100644 index 90d23d9..0000000 --- a/tests/pdf_test.py +++ /dev/null @@ -1,7 +0,0 @@ -# import pdf_client -# -# client = pdf_client.PdfClient({'apiUser': 'foo', 'apiKey': 'bar'}, 'bumagent') -# -# pdf = client.fetch_shipment_labels('s2MQ73VJWh6LS') -# -# print(pdf) \ No newline at end of file diff --git a/tests/test_accounts.py b/tests/test_accounts.py deleted file mode 100644 index 45aacff..0000000 --- a/tests/test_accounts.py +++ /dev/null @@ -1,35 +0,0 @@ -from despatchbay.despatchbay_sdk import DespatchBaySDK -import pprint - -client = DespatchBaySDK(api_user='2MTN-C5I9R355', api_key='114LS25D', api_domain='api.despatchbay.st') -# client = DespatchBaySDK(api_user='D2N3C-7DB9EC51', api_key='676BE721E048AFA69151') -account_return = client.get_account() -print(account_return.__dict__) -print(account_return.to_soap_object()) - -services_return = client.get_services() -for returned_service in services_return: - print(returned_service.__dict__) - -print('='*20) -account_balance = client.get_account_balance() -print(account_balance.balance) -print(account_balance.available) - -print('='*20) -sender_addresses = client.get_sender_addresses() -print(sender_addresses[0].__dict__) -print(sender_addresses[0].sender_address.__dict__) - -print('='*20) -payment_methods = client.get_payment_methods() -for payment_method in payment_methods: - pprint.pprint(payment_method.__dict__) - print(payment_method.to_soap_object()) - -print('='*20) -print('Automatic topup enabled?', client.enable_automatic_topups('100', payment_methods[0].payment_method_id, payment_methods[0].payment_method_id)) - - -print('='*20) -print('Automatic topups disabled?', client.disable_automatic_topups()) diff --git a/tests/test_addresses.py b/tests/test_addresses.py deleted file mode 100644 index 3cc4c44..0000000 --- a/tests/test_addresses.py +++ /dev/null @@ -1,17 +0,0 @@ -import despatchbay_sdk - -client = despatchbay_sdk.DespatchBaySDK(api_user='2MTN-C5I9R355', api_key='114LS25D', api_domain='api.despatchbay.st') -# client = despatchbay_sdk.DespatchBaySDK(api_user='D2N3C-7DB9EC51', api_key='676BE721E048AFA69151') -print(client.addressing_client) - -address = client.find_address('ln69zd', '17') -print(address.__dict__) -print(address.to_soap_object()) - -address = client.get_address_by_key('wd65jb1015') -print(address.__dict__) -print(address.to_soap_object()) - -for address in client.get_address_keys_by_postcode('wd6 5jb'): - print(address.__dict__) - print(address.to_soap_object()) diff --git a/tests/test_shipping.py b/tests/test_shipping.py deleted file mode 100644 index 77e9b14..0000000 --- a/tests/test_shipping.py +++ /dev/null @@ -1,48 +0,0 @@ - - - -# normal -client = despatchbay_sdk.DespatchBaySDK(api_user='2MTN-C5I9R355', api_key='114LS25D', api_domain='api.despatchbay.st') -#demo -# self.client = DBPAPI.DespatchBayAPI(apiuser='2MTN-3C7B9E45', apikey='7E33ABC14613E3251846') - -my_parcel_1 = client.parcel(weight=1, length=1, width=1, height=1, contents=1, value=1, tracking_number=None) -print(my_parcel_1.__dict__) -print(my_parcel_1.to_soap_object()) -my_parcel_2 = client.parcel(weight=30, length=99, width=99, height=99, contents='balloons', value=1) -print(my_parcel_2.__dict__) -print(my_parcel_2.to_soap_object()) -my_address = client.address( - company_name="Acme", - country_code="GB", - county="", - locality="", - postal_code="sw1a1aa", - town_city="London", - street="Buckingham Palace" -) -print(my_address.__dict__) -print(my_address.to_soap_object()) -recipient = client.recipient(name="scott", telephone="foo", email="bar", recipient_address=my_address) -sender = client.sender(name="Al", telephone="foo", email="bar", sender_address=my_address) -shipment_request = client.shipment_request(parcels=[my_parcel_1, my_parcel_2], client_reference='puchacz', - collection_date='2019-04-10', sender_address=sender, - recipient_address=recipient, follow_shipment='true') -print(shipment_request.to_soap_object()) -services = client.get_available_services(shipment_request) -for service in services: - print(service.courier.to_soap_object()) -shipment_request.service_id = services[0].service_id -dates = client.get_available_collection_dates(sender, services[0].courier.courier_id) -print(dates) -shipment_request.collection_date = dates[0] -print(shipment_request.__dict__) -print(shipment_request.collection_date.__dict__) -# print('+++++++++++++++++++++') -# print(shipment_request.collection_date.to_soap_object()) -# print('+++++++++++++++++++++') -# added_shipment = client.add_shipment(shipment_request) -# client.book_shipments([added_shipment]) -# shipment_return = client.get_shipment(added_shipment) -# label_pdf = client.fetch_shipment_labels(shipment_return.shipment_document_id) -# label_pdf.download('./new_pdf.pdf') From 1cc95101a12ae9d04e54bc2bac63a9ceb0e6acd1 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Fri, 26 Apr 2019 16:06:05 +0100 Subject: [PATCH 26/41] Handling of address id in a sender --- despatchbay/despatchbay_entities.py | 15 +++++++++++---- despatchbay/despatchbay_sdk.py | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/despatchbay/despatchbay_entities.py b/despatchbay/despatchbay_entities.py index 6a22bd9..9d92ab3 100644 --- a/despatchbay/despatchbay_entities.py +++ b/despatchbay/despatchbay_entities.py @@ -50,7 +50,6 @@ class Account(Entity): 'AccountBalance': { 'property': 'balance', 'type': 'entity', - # 'entityClass': 'DespatchBay\Entity\AccountBalance' } } SOAP_TYPE = 'ns1:AccountType' @@ -524,7 +523,6 @@ def from_dict(cls, client, soap_dict): class Sender(Entity): - # todo handle sender address id SOAP_MAP = { 'SenderName': { 'property': 'name', @@ -554,7 +552,10 @@ def __init__(self, client, name=None, telephone=None, email=None, sender_address self.name = name self.telephone = telephone self.email = email - self.sender_address = sender_address + if sender_address: + self.sender_address = sender_address + else: + self.sender_address = Address(client) self.address_id = address_id @classmethod @@ -572,9 +573,15 @@ def from_dict(cls, client, soap_dict): client, client.shipping_client.dict(soap_dict.get('SenderAddress', None)) ), - address_id = soap_dict.get('SenderAddressID') + address_id=soap_dict.get('SenderAddressID') ) + def to_soap_object(self): + object = super().to_soap_object() + if object.SenderAddressID: + object.SenderAddress = None + return object + class Service(Entity): SOAP_MAP = { diff --git a/despatchbay/despatchbay_sdk.py b/despatchbay/despatchbay_sdk.py index d2aaafc..04065a8 100644 --- a/despatchbay/despatchbay_sdk.py +++ b/despatchbay/despatchbay_sdk.py @@ -5,7 +5,7 @@ def handle_suds_fault(error): - exception_info = error.args[0] + exception_info = error.args[0].decode() if 'Unauthorized' in exception_info: raise exceptions.AuthorizationException('Invalid API credentials') from error elif 'Could not connect to host' in exception_info: From 3e9d90934ab7c0c5f57f145c5639ba41c13f77e3 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Mon, 29 Apr 2019 11:42:54 +0100 Subject: [PATCH 27/41] Pylint changes --- despatchbay/despatchbay_entities.py | 102 ++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 6 deletions(-) diff --git a/despatchbay/despatchbay_entities.py b/despatchbay/despatchbay_entities.py index 9d92ab3..82066a6 100644 --- a/despatchbay/despatchbay_entities.py +++ b/despatchbay/despatchbay_entities.py @@ -1,5 +1,11 @@ -class Entity(object): +""" +Entities relating to types in the Despatchbay v15 api +https://github.com/despatchbay/despatchbay-api-v15/wiki +""" + +class Entity: + """Base class for Despatchbay entities""" def __init__(self, soap_type, soap_client, soap_map): self.soap_type = soap_type self.soap_client = soap_client @@ -38,6 +44,11 @@ def to_soap_object(self): class Account(Entity): + """ + Represents a Despactchbay API AccountType entity. + + https://github.com/despatchbay/despatchbay-api-v15/wiki/Account-Service#accounttype + """ SOAP_MAP = { 'AccountID': { 'property': 'account_id', @@ -78,6 +89,11 @@ def from_dict(cls, client, soap_dict): class AccountBalance(Entity): + """ + Represents a Despactchbay API AccountBalanceType entity. + + https://github.com/despatchbay/despatchbay-api-v15/wiki/Account-Service#accountbalancetype + """ SOAP_MAP = { 'Balance': { 'property': 'balance', @@ -109,7 +125,11 @@ def from_dict(cls, client, soap_dict): class Address(Entity): + """ + Represents a Despactchbay API AddressType entity. + https://github.com/despatchbay/despatchbay-api-v15/wiki/Account-Service#addresstype + """ SOAP_MAP = { 'CompanyName': { 'property': 'company_name', @@ -172,6 +192,11 @@ def from_dict(cls, client, soap_dict): class AddressKey(Entity): + """ + Represents a Despactchbay API AddressKeyType entity. + + https://github.com/despatchbay/despatchbay-api-v15/wiki/Addressing-Service#addresskeytype + """ SOAP_MAP = { 'Key': { 'property': 'key', @@ -203,6 +228,11 @@ def from_dict(cls, client, soap_dict): class AutomaticTopupSettings(Entity): + """ + Represents a Despactchbay API AutomaticTopupSettings entity. + + https://github.com/despatchbay/despatchbay-api-v15/wiki/Account-Service#automatictopupsettings + """ SOAP_MAP = { "MinimumBalance": { "property": "minimum_balance", @@ -240,7 +270,11 @@ def from_dict(cls, client, soap_dict): class Collection(Entity): + """ + Represents a Despactchbay API CollectionType entity. + https://github.com/despatchbay/despatchbay-api-v15/wiki/Shipping-Service#collectionreturntype + """ SOAP_MAP = { "CollectionID": { "property": "collection_id", @@ -318,6 +352,11 @@ def from_dict(cls, client, soap_dict): class CollectionDate(Entity): + """ + Represents a Despactchbay API CollectionDateType entity. + + https://github.com/despatchbay/despatchbay-api-v15/wiki/Shipping-Service#collectiondatetype + """ SOAP_MAP = { "CollectionDate": { "property": "date", @@ -343,7 +382,11 @@ def from_dict(cls, client, soap_dict): class Courier(Entity): + """ + Represents a Despactchbay API CourierType entity. + https://github.com/despatchbay/despatchbay-api-v15/wiki/Shipping-Service#couriertype + """ SOAP_MAP = { 'CourierID': { 'property': 'courier_id', @@ -375,7 +418,11 @@ def from_dict(cls, client, soap_dict): class Parcel(Entity): + """ + Represents a Despactchbay API ParcelType entity. + https://github.com/despatchbay/despatchbay-api-v15/wiki/Shipping-Service#parceltype + """ SOAP_MAP = { 'Weight': { 'property': 'weight', @@ -439,7 +486,11 @@ def from_dict(cls, client, soap_dict): class PaymentMethod(Entity): + """ + Represents a Despactchbay API PaymentMethodType entity. + https://github.com/despatchbay/despatchbay-api-v15/wiki/Account-Service#paymentmethodtype + """ SOAP_MAP = { 'PaymentMethodID': { 'property': 'payment_method_id', @@ -477,6 +528,11 @@ def from_dict(cls, client, soap_dict): class Recipient(Entity): + """ + Represents a Despactchbay API RecipientAddressType entity. + + https://github.com/despatchbay/despatchbay-api-v15/wiki/Shipping-Service#recipientaddresstype + """ SOAP_MAP = { 'RecipientName': { 'property': 'name', @@ -523,6 +579,11 @@ def from_dict(cls, client, soap_dict): class Sender(Entity): + """ + Represents a Despactchbay API SenderAddressType entity. + + https://github.com/despatchbay/despatchbay-api-v15/wiki/Shipping-Service#senderaddresstype + """ SOAP_MAP = { 'SenderName': { 'property': 'name', @@ -577,13 +638,23 @@ def from_dict(cls, client, soap_dict): ) def to_soap_object(self): - object = super().to_soap_object() - if object.SenderAddressID: - object.SenderAddress = None + """ + Creates a SOAP client object representation of this entity. + + Removes sender address property if a sender address id is provided. + """ + soap_object = super().to_soap_object() + if soap_object.SenderAddressID: + soap_object.SenderAddress = None return object class Service(Entity): + """ + Represents a Despactchbay API ServiceType entity. + + https://github.com/despatchbay/despatchbay-api-v15/wiki/Shipping-Service#servicetype + """ SOAP_MAP = { 'ServiceID': { 'property': 'service_id', @@ -636,6 +707,11 @@ def from_dict(cls, client, soap_dict): class ShipmentRequest(Entity): + """ + Represents a Despactchbay API ShipmentRequest entity. + + https://github.com/despatchbay/despatchbay-api-v15/wiki/Shipping-Service#shipmentrequesttype + """ SOAP_MAP = { 'ServiceID': { 'property': 'service_id', @@ -682,21 +758,35 @@ def __init__(self, client, service_id=None, parcels=None, client_reference=None, self.follow_shipment = follow_shipment def validate_collection_date_object(self, collection_date): + """ + Converts a string timestamp to a CollectionDate object. + """ if isinstance(collection_date, str): return CollectionDate(self._despatchbay_client, date=collection_date) - else: - return collection_date + return collection_date @property def collection_date(self): + """ + Returns the private attribute collection_date + """ return self._collection_date @collection_date.setter def collection_date(self, collection_date): + """ + Allows the collection date to be set from just a timestamp string but limits the + type of the _collection_date attribute to a CollectionDate object. + """ self._collection_date = self.validate_collection_date_object(collection_date) class ShipmentReturn(Entity): + """ + Represents a Despactchbay API ShipmentReturnType entity. + + https://github.com/despatchbay/despatchbay-api-v15/wiki/Shipping-Service#shipmentreturntype + """ SOAP_MAP = { 'ShipmentID': { 'property': 'shipment_id', From 68d9eb626757561a866b8e1b75c4b5b40ff86fcf Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Mon, 29 Apr 2019 11:55:02 +0100 Subject: [PATCH 28/41] Pylint changes --- despatchbay/despatchbay_sdk.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/despatchbay/despatchbay_sdk.py b/despatchbay/despatchbay_sdk.py index 04065a8..9a01512 100644 --- a/despatchbay/despatchbay_sdk.py +++ b/despatchbay/despatchbay_sdk.py @@ -1,3 +1,9 @@ +""" +Client for the Despatchbay v15 api +https://github.com/despatchbay/despatchbay-api-v15/wiki +""" + + from suds.client import Client from suds.transport.http import HttpAuthenticated import suds @@ -5,31 +11,37 @@ def handle_suds_fault(error): + """ + Throws despatchbaysdk exceptions in response to suds exceptions + """ exception_info = error.args[0].decode() if 'Unauthorized' in exception_info: raise exceptions.AuthorizationException('Invalid API credentials') from error - elif 'Could not connect to host' in exception_info: + if 'Could not connect to host' in exception_info: raise exceptions.ConnectionException('Failed to connect to the Despatch Bay API') from error - elif 'Your access rate limit for this service has been exceeded' in exception_info: + if 'Your access rate limit for this service has been exceeded' in exception_info: raise exceptions.RateLimitException(exception_info) - else: - raise exceptions.ApiException(error) from error + raise exceptions.ApiException(error) from error -def try_except(fn): +def try_except(function): """ A decorator to catch suds exceptions """ def wrapped(*args, **kwargs): try: - return fn(*args, **kwargs) + return function(*args, **kwargs) except suds.WebFault as detail: handle_suds_fault(detail) return wrapped -class DespatchBaySDK(object): +class DespatchBaySDK: + """ + Client for despatchbay v15 api. + https://github.com/despatchbay/despatchbay-api-v15/wiki + """ def __init__(self, api_user, api_key, api_domain='api.despatchbay.com', api_version='15'): soap_url_template = 'http://{}/soap/v{}/{}?wsdl' documents_url = 'http://{}/documents/v1'.format(api_domain) @@ -49,6 +61,9 @@ def __init__(self, api_user, api_key, api_domain='api.despatchbay.com', api_vers @staticmethod def create_transport(username, password): + """ + HTTP transport providing authentication for suds. + """ return HttpAuthenticated(username=username, password=password) # Shipping entities From 5febbda7a0b41e0404db3dc7b1199a70743e4e98 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Mon, 29 Apr 2019 12:04:12 +0100 Subject: [PATCH 29/41] Pylint changes --- despatchbay/documents_client.py | 38 ++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/despatchbay/documents_client.py b/despatchbay/documents_client.py index cc0acd4..434d132 100644 --- a/despatchbay/documents_client.py +++ b/despatchbay/documents_client.py @@ -1,9 +1,23 @@ +""" +Classes for working with the despatchbay documents api + +https://github.com/despatchbay/despatchbay-api-v15/wiki/Documents-API +""" + from urllib.parse import urlencode -from . import exceptions -import requests import base64 -class Document(object): +import requests + +from . import exceptions + + +class Document: + """ + A document (label/manifest) in the despatchbay documents api. + + https://github.com/despatchbay/despatchbay-api-v15/wiki/Documents-API + """ def __init__(self, data): if self.is_pdf(data): self.data = data @@ -38,7 +52,12 @@ def download(self, path): document_file.write(self.data) -class DocumentsClient(object): +class DocumentsClient: + """ + Client for the despatchbay documents api + + https://github.com/despatchbay/despatchbay-api-v15/wiki/Documents-API + """ def __init__(self, api_url='http://api.despatchbay.com/documents/v1'): self.api_url = api_url @@ -49,16 +68,15 @@ def handle_response_code(code): """ if code == 200: return True - elif code == 400: + if code == 400: raise exceptions.InvalidArgumentException('The PDF Labels API was unable to process the request') - elif code == 401: + if code == 401: raise exceptions.AuthorizationException('Unauthorized') - elif code == 402: + if code == 402: raise exceptions.PaymentException('Insufficient Despatch Bay account balance') - elif code == 404: + if code == 404: raise exceptions.ApiException('Unknown shipment ID') - else: - raise exceptions.ApiException('An unexpected error occurred (HTTP {})'.format(code)) + raise exceptions.ApiException('An unexpected error occurred (HTTP {})'.format(code)) def fetch_shipment_labels(self, ship_collect_ids, layout=None, label_format=None, label_dpi=None): """ From d6cc4e94ddfb73571847ef991f458381de05ef20 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Mon, 29 Apr 2019 12:09:24 +0100 Subject: [PATCH 30/41] Pylint changes --- despatchbay/exceptions.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/despatchbay/exceptions.py b/despatchbay/exceptions.py index 4ac7cad..ac227b5 100644 --- a/despatchbay/exceptions.py +++ b/despatchbay/exceptions.py @@ -1,27 +1,29 @@ +"""Despatchbay SDK exceptions""" + + class Error(Exception): """Base class for other exceptions""" - pass class InvalidArgumentException(Error): - pass + """Exception to raise when invalid arguments are passed.""" class AuthorizationException(Error): - pass + """Exception to raise when a 401 error is returned.""" class PaymentException(Error): - pass + """Exception to raise when an operation fails due to insufficient funds.""" class ApiException(Error): - pass + """General API exception""" class ConnectionException(Error): - pass + """General connection error exception.""" class RateLimitException(Error): - pass + """Exception to raise when an operation fails due rate limits.""" From 9c27b521d88d7815ba3928c30ccf4ad7a6600c8f Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Tue, 30 Apr 2019 15:11:37 +0100 Subject: [PATCH 31/41] Example usage files --- examples/__init__.py | 0 examples/accounts.py | 25 ++++++++++++ examples/addresses.py | 12 ++++++ examples/shipping_and_documents.py | 63 ++++++++++++++++++++++++++++++ examples/tracking.py | 6 +++ 5 files changed, 106 insertions(+) create mode 100644 examples/__init__.py create mode 100644 examples/accounts.py create mode 100644 examples/addresses.py create mode 100644 examples/shipping_and_documents.py create mode 100644 examples/tracking.py diff --git a/examples/__init__.py b/examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/accounts.py b/examples/accounts.py new file mode 100644 index 0000000..8ed79bb --- /dev/null +++ b/examples/accounts.py @@ -0,0 +1,25 @@ +from despatchbay.despatchbay_sdk import DespatchBaySDK + +client = DespatchBaySDK(api_user='', api_key='') + +account_return = client.get_account() +print(account_return) + +services_return = client.get_services() +print(services_return) + +account_balance = client.get_account_balance() +print(account_balance) + +sender_addresses = client.get_sender_addresses() +print(sender_addresses) + +payment_methods = client.get_payment_methods() +print(payment_methods) + +automatic_topup_enabled = client.enable_automatic_topups( + '100', payment_methods[0].payment_method_id, payment_methods[0].payment_method_id) +print(automatic_topup_enabled) + +automatic_topup_disabled = client.disable_automatic_topups() +print(automatic_topup_disabled) diff --git a/examples/addresses.py b/examples/addresses.py new file mode 100644 index 0000000..05b1a30 --- /dev/null +++ b/examples/addresses.py @@ -0,0 +1,12 @@ +from despatchbay.despatchbay_sdk import DespatchBaySDK + +client = DespatchBaySDK(api_user='', api_key='') + +address_1 = client.find_address('DN227AY', '1') +print(address_1) + +address_2 = client.get_address_by_key('DN227AY0001') +print(address_2) + +address_3 = client.get_address_keys_by_postcode('DN22 7AY') +print(address_3) diff --git a/examples/shipping_and_documents.py b/examples/shipping_and_documents.py new file mode 100644 index 0000000..386f0ad --- /dev/null +++ b/examples/shipping_and_documents.py @@ -0,0 +1,63 @@ +from despatchbay.despatchbay_sdk import DespatchBaySDK + +client = DespatchBaySDK(api_user='', api_key='') + +my_parcel_1 = client.parcel( + weight=3, + length=14, + width=15, + height=92, + contents='Apples', + value=65 +) +my_parcel_2 = client.parcel( + weight=30, + length=100, + width=100, + height=100, + contents='Oranges', + value=1 +) +recipient_address = client.address( + company_name="Acme", + country_code="GB", + county="Theshire", + locality="Placeton", + postal_code="ps76de", + town_city="Cityville", + street="123 Fake Street" +) + +recipient = client.recipient( + name="Bonnie Bobbins", + telephone="01632987654", + email="bonnie@example.com", + recipient_address=recipient_address + +) +# Sender using address_id +sender = client.sender( + name="Joe Bloggs", + telephone="01632123456", + email="acme@example.com", + address_id='123456' +) + +shipment_request = client.shipment_request( + parcels=[my_parcel_1, my_parcel_2], + client_reference='puchacz', + collection_date='2019-04-01', + sender_address=sender, + recipient_address=recipient, + follow_shipment='true' +) + +services = client.get_available_services(shipment_request) +shipment_request.service_id = services[0].service_id +dates = client.get_available_collection_dates(sender, services[0].courier.courier_id) +shipment_request.collection_date = dates[0] +added_shipment = client.add_shipment(shipment_request) +client.book_shipments([added_shipment]) +shipment_return = client.get_shipment(added_shipment) +label_pdf = client.fetch_shipment_labels(shipment_return.shipment_document_id) +label_pdf.download('./new_label.pdf') diff --git a/examples/tracking.py b/examples/tracking.py new file mode 100644 index 0000000..6f094d5 --- /dev/null +++ b/examples/tracking.py @@ -0,0 +1,6 @@ +from despatchbay.despatchbay_sdk import DespatchBaySDK + +client = DespatchBaySDK(api_user='', api_key=' Date: Thu, 2 May 2019 11:16:32 +0100 Subject: [PATCH 32/41] Fixes from testing --- despatchbay/despatchbay_entities.py | 33 +++++++++++++++++--- despatchbay/despatchbay_sdk.py | 33 +++++++++++++++++--- despatchbay/documents_client.py | 48 ++++++++++++----------------- setup.py | 20 ++++++------ 4 files changed, 87 insertions(+), 47 deletions(-) diff --git a/despatchbay/despatchbay_entities.py b/despatchbay/despatchbay_entities.py index 82066a6..dca581e 100644 --- a/despatchbay/despatchbay_entities.py +++ b/despatchbay/despatchbay_entities.py @@ -42,6 +42,12 @@ def to_soap_object(self): ) return suds_object + def __str__(self): + return str(self.to_soap_object()) + + def __repr__(self): + return self.__str__() + class Account(Entity): """ @@ -61,6 +67,7 @@ class Account(Entity): 'AccountBalance': { 'property': 'balance', 'type': 'entity', + 'soap_type': 'ns1:AccountBalanceType', } } SOAP_TYPE = 'ns1:AccountType' @@ -548,7 +555,8 @@ class Recipient(Entity): }, 'RecipientAddress': { 'property': 'recipient_address', - 'type': 'entity' + 'type': 'entity', + 'soap_type': 'ns1:RecipientAddressType', }, } SOAP_TYPE = 'ns1:RecipientAddressType' @@ -600,6 +608,8 @@ class Sender(Entity): 'SenderAddress': { 'property': 'sender_address', 'type': 'entity', + 'soap_type': 'ns1:SenderAddress', + }, 'SenderAddressID': { 'property': 'address_id', @@ -646,7 +656,7 @@ def to_soap_object(self): soap_object = super().to_soap_object() if soap_object.SenderAddressID: soap_object.SenderAddress = None - return object + return soap_object class Service(Entity): @@ -675,6 +685,8 @@ class Service(Entity): 'Courier': { 'property': 'courier', 'type': 'entity', + 'soap_type': 'ns1:CourierType', + }, } SOAP_TYPE = 'ns1:ServiceType' @@ -729,14 +741,17 @@ class ShipmentRequest(Entity): 'CollectionDate': { 'property': 'collection_date', 'type': 'entity', + 'soap_type': 'ns1:CollectionDateType', }, 'RecipientAddress': { 'property': 'recipient_address', 'type': 'entity', + 'soap_type': 'ns1:RecipientAddressType', }, 'SenderAddress': { 'property': 'sender_address', 'type': 'entity', + 'soap_type': 'ns1:SenderAddressType', }, 'FollowShipment': { 'property': 'follow_shipment', @@ -800,6 +815,10 @@ class ShipmentReturn(Entity): 'property': 'collection_id', 'type': 'string', }, + 'CollectionDocumentID': { + 'property': 'collection_document_id', + 'type': 'string', + }, 'ServiceID': { 'property': 'service_id', 'type': 'string', @@ -807,6 +826,7 @@ class ShipmentReturn(Entity): 'Parcels': { 'property': 'parcels', 'type': 'entityArray', + 'soap_type': 'ns1:ArrayOfParcelType', }, 'ClientReference': { 'property': 'client_reference', @@ -815,6 +835,7 @@ class ShipmentReturn(Entity): 'RecipientAddress': { 'property': 'recipient_address', 'type': 'entity', + 'soap_type': 'ns1:RecipientAddressType', }, 'IsFollowed': { 'property': 'is_followed', @@ -844,14 +865,15 @@ class ShipmentReturn(Entity): SOAP_TYPE = 'ns1:ShipmentReturnType' def __init__(self, client, shipment_id=None, shipment_document_id=None, collection_id=None, - service_id=None, parcels=None, client_reference=None, recipient_address=None, - is_followed=None, is_printed=None, is_despatched=None, is_delivered=None, - is_cancelled=None, labels_url=None): + collection_document_id=None, service_id=None, parcels=None, client_reference=None, + recipient_address=None, is_followed=None, is_printed=None, is_despatched=None, + is_delivered=None, is_cancelled=None, labels_url=None): super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) self.despatchbay_client = client self.shipment_id = shipment_id self.shipment_document_id = shipment_document_id self.collection_id = collection_id + self.collection_document_id = collection_document_id self.service_id = service_id self.parcels = parcels self.client_reference = client_reference @@ -882,6 +904,7 @@ def from_dict(cls, client, soap_dict): shipment_id=soap_dict.get('ShipmentID'), shipment_document_id=soap_dict.get('ShipmentDocumentID'), collection_id=soap_dict.get('CollectionID'), + collection_document_id=soap_dict.get('CollectionDocumentID'), service_id=soap_dict.get('ServiceID'), parcels=parcel_array, client_reference=soap_dict.get('ClientReference'), diff --git a/despatchbay/despatchbay_sdk.py b/despatchbay/despatchbay_sdk.py index 9a01512..dddd644 100644 --- a/despatchbay/despatchbay_sdk.py +++ b/despatchbay/despatchbay_sdk.py @@ -14,16 +14,36 @@ def handle_suds_fault(error): """ Throws despatchbaysdk exceptions in response to suds exceptions """ - exception_info = error.args[0].decode() + try: + exception_info = error.args[0].decode() + except AttributeError: + exception_info = error.args[0] if 'Unauthorized' in exception_info: raise exceptions.AuthorizationException('Invalid API credentials') from error if 'Could not connect to host' in exception_info: raise exceptions.ConnectionException('Failed to connect to the Despatch Bay API') from error if 'Your access rate limit for this service has been exceeded' in exception_info: raise exceptions.RateLimitException(exception_info) + # Re-raise general suds exceptions as exceptions.ApiException raise exceptions.ApiException(error) from error +def handle_suds_generic_fault(error): + """ + Throws despatchbaysdk exceptions in response to suds generic 'Exception' exceptions + """ + try: + exception_info = error.args[0].decode() + except AttributeError: + exception_info = error.args[0] + if 401 in exception_info: + raise exceptions.AuthorizationException('Invalid API credentials') from error + if 429 in exception_info: + raise exceptions.RateLimitException(exception_info[1]) + # Re-raise original error + raise error + + def try_except(function): """ A decorator to catch suds exceptions @@ -33,6 +53,9 @@ def wrapped(*args, **kwargs): return function(*args, **kwargs) except suds.WebFault as detail: handle_suds_fault(detail) + except Exception as detail: + handle_suds_generic_fault(detail) + return wrapped @@ -338,14 +361,14 @@ def get_tracking(self, tracking_number): # Documents services - def fetch_shipment_labels(self, document_id, **kwargs): + def fetch_shipment_labels(self, shipment_document_id, **kwargs): """ Fetches labels from the Despatch Bay documents API. """ - return self.pdf_client.fetch_shipment_labels(document_id, **kwargs) + return self.pdf_client.fetch_shipment_labels(shipment_document_id, **kwargs) - def fetch_manifest(self, collection_id): + def fetch_manifest(self, collection_document_id, **kwargs): """ Fetches manifests from the Despatch Bay documents API. """ - return self.pdf_client.fetch_manifest(collection_id) + return self.pdf_client.fetch_manifest(collection_document_id, **kwargs) diff --git a/despatchbay/documents_client.py b/despatchbay/documents_client.py index 434d132..a24aa86 100644 --- a/despatchbay/documents_client.py +++ b/despatchbay/documents_client.py @@ -19,18 +19,7 @@ class Document: https://github.com/despatchbay/despatchbay-api-v15/wiki/Documents-API """ def __init__(self, data): - if self.is_pdf(data): - self.data = data - else: - raise TypeError("File returned from api is not a valid PDF.") - - @staticmethod - def is_pdf(data): - """ - Performs a rudimentary check to see if the data APPEARS to be a - valid POF file. - """ - return data[0:4].decode() == '%PDF' + self.data = data def get_raw(self): """ @@ -40,9 +29,9 @@ def get_raw(self): def get_base64(self): """ - Base 64 encodes the PDF data before returning it. + Base 64 encodes the document data before returning it. """ - return base64.b64decode(self.data) + return base64.b64encode(self.data) def download(self, path): """ @@ -62,14 +51,15 @@ def __init__(self, api_url='http://api.despatchbay.com/documents/v1'): self.api_url = api_url @staticmethod - def handle_response_code(code): + def handle_response_code(code, response): """ Returns true if code is 200, otherwise raises an appropriate exception. """ if code == 200: return True if code == 400: - raise exceptions.InvalidArgumentException('The PDF Labels API was unable to process the request') + raise exceptions.InvalidArgumentException( + 'The Documents API was unable to process the request: {}'.format(response)) if code == 401: raise exceptions.AuthorizationException('Unauthorized') if code == 402: @@ -78,17 +68,17 @@ def handle_response_code(code): raise exceptions.ApiException('Unknown shipment ID') raise exceptions.ApiException('An unexpected error occurred (HTTP {})'.format(code)) - def fetch_shipment_labels(self, ship_collect_ids, layout=None, label_format=None, label_dpi=None): + def fetch_shipment_labels(self, shipment_document_ids, label_layout=None, label_format=None, label_dpi=None): """ - Returns a pdf entity of the shipment labels identified by ship_collect_ids. + Returns a document entity of the shipment labels identified by ship_collect_ids. """ - if isinstance(ship_collect_ids, list): - shipment_string = ','.join(ship_collect_ids) + if isinstance(shipment_document_ids, list): + shipment_string = ','.join(shipment_document_ids) else: - shipment_string = ship_collect_ids + shipment_string = shipment_document_ids query_dict = {} - if layout: - query_dict['layout'] = layout + if label_layout: + query_dict['layout'] = label_layout if label_format: query_dict['format'] = label_format if label_format == 'png_base64' and label_dpi: @@ -99,14 +89,16 @@ def fetch_shipment_labels(self, ship_collect_ids, layout=None, label_format=None query_string = urlencode(query_dict) label_request_url = label_request_url + '?' + query_string response = requests.get(label_request_url) - self.handle_response_code(response.status_code) + self.handle_response_code(response.status_code, response.text) return Document(response.content) - def fetch_manifest(self, collection_id): + def fetch_manifest(self, collection_document_id, manifest_format=None): """ - Returns a pdf entity of the shipment manifest identified by collection_id. + Returns a document entity of the shipment manifest identified by collection_id. """ - manifest_request_url = '{}/manifest/{}'.format(self.api_url, collection_id) + manifest_request_url = '{}/manifest/{}'.format(self.api_url, collection_document_id) + if manifest_format: + manifest_request_url = '{}?format={}'.format(manifest_request_url, manifest_format) response = requests.get(manifest_request_url) - self.handle_response_code(response.status_code) + self.handle_response_code(response.status_code, response.text) return Document(response.content) diff --git a/setup.py b/setup.py index a5a45ec..49a2495 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,13 @@ from setuptools import setup -setup(name='despatchbay', - version='0.1', - description='Despatchbay SDK', - url='', - author='', - author_email='', - license='MIT', - packages=['despatchbay'], - zip_safe=False) +setup( + name='despatchbay', + version='0.1', + description='Despatchbay SDK', + url='', + author='', + author_email='', + license='MIT', + packages=['despatchbay'], + zip_safe=False +) From a7bf56723266f1732e6526c104d469123283f7bc Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Thu, 2 May 2019 14:41:37 +0100 Subject: [PATCH 33/41] setup chnages --- README.md | 1 + setup.py | 33 ++++++++++++++++++++++----------- 2 files changed, 23 insertions(+), 11 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..44a2766 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Despatch Bay Python SDK diff --git a/setup.py b/setup.py index 49a2495..bf3e864 100644 --- a/setup.py +++ b/setup.py @@ -1,13 +1,24 @@ -from setuptools import setup +import setuptools -setup( - name='despatchbay', - version='0.1', - description='Despatchbay SDK', - url='', - author='', - author_email='', - license='MIT', - packages=['despatchbay'], - zip_safe=False +with open("README.md", "r") as fh: + long_description = fh.read() + +print(setuptools.find_packages()) + +setuptools.setup( + name="despatchbay", + version="0.0.5", + author="Despatch Bay", + author_email="scott.keenan@thesalegroup.co.uk", + description="Python SDK for the Despatch Bay API v15", + install_requires=['suds-py3', 'requests'], + long_description=long_description, + long_description_content_type="text/markdown", + url="", + packages=setuptools.find_packages(), + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], ) From 96e8059ad6787163a2ebcb5c7ea2cf8dc931a2d3 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Wed, 3 Jul 2019 17:49:24 +0100 Subject: [PATCH 34/41] Removed empty __init__.py --- despatchbay/__init__.py | 0 examples/__init__.py | 0 setup.py | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 despatchbay/__init__.py delete mode 100644 examples/__init__.py diff --git a/despatchbay/__init__.py b/despatchbay/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/examples/__init__.py b/examples/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/setup.py b/setup.py index bf3e864..f11f41d 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ long_description=long_description, long_description_content_type="text/markdown", url="", - packages=setuptools.find_packages(), + packages=setuptools.find_namespace_packages(include=['despatchbay-python-sdk.despatchbay']), classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", From 892b815f7b60d472df650cc840189bde8000bcf6 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Wed, 3 Jul 2019 17:52:58 +0100 Subject: [PATCH 35/41] Minimum version number --- despatchbay/despatchbay_sdk.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/despatchbay/despatchbay_sdk.py b/despatchbay/despatchbay_sdk.py index dddd644..10856e3 100644 --- a/despatchbay/despatchbay_sdk.py +++ b/despatchbay/despatchbay_sdk.py @@ -65,13 +65,17 @@ class DespatchBaySDK: https://github.com/despatchbay/despatchbay-api-v15/wiki """ - def __init__(self, api_user, api_key, api_domain='api.despatchbay.com', api_version='15'): + def __init__(self, api_user, api_key, api_domain='api.despatchbay.com', despactchbay_api_version=15, documents_api_version=1): + if despactchbay_api_version < 15: + raise exceptions.ApiException("DespatchBay API version must be 15 or higher.") + if documents_api_version < 1: + raise exceptions.ApiException("Documents API version must be 1 or higher.") soap_url_template = 'http://{}/soap/v{}/{}?wsdl' - documents_url = 'http://{}/documents/v1'.format(api_domain) - account_url = soap_url_template.format(api_domain, api_version, 'account') - shipping_url = soap_url_template.format(api_domain, api_version, 'shipping') - addressing_url = soap_url_template.format(api_domain, api_version, 'addressing') - tracking_url = soap_url_template.format(api_domain, api_version, 'tracking') + documents_url = 'http://{}/documents/v{}'.format(api_domain, documents_api_version) + account_url = soap_url_template.format(api_domain, despactchbay_api_version, 'account') + shipping_url = soap_url_template.format(api_domain, despactchbay_api_version, 'shipping') + addressing_url = soap_url_template.format(api_domain, despactchbay_api_version, 'addressing') + tracking_url = soap_url_template.format(api_domain, despactchbay_api_version, 'tracking') self.account_client = Client( account_url, transport=self.create_transport(api_user, api_key)) self.addressing_client = Client( @@ -285,7 +289,7 @@ def get_collection(self, collection_id): """ collection_dict = self.shipping_client.dict( self.shipping_client.service.GetCollection(collection_id)) - return despatchbay_entities.Address.from_dict( + return despatchbay_entities.Collection.from_dict( self, collection_dict ) From 5075577f695866811945cb8b0b8757455e968e72 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Wed, 3 Jul 2019 17:53:40 +0100 Subject: [PATCH 36/41] Book/print shipments, print collection labels and manifests --- despatchbay/despatchbay_entities.py | 43 +++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/despatchbay/despatchbay_entities.py b/despatchbay/despatchbay_entities.py index dca581e..939df43 100644 --- a/despatchbay/despatchbay_entities.py +++ b/despatchbay/despatchbay_entities.py @@ -42,11 +42,8 @@ def to_soap_object(self): ) return suds_object - def __str__(self): - return str(self.to_soap_object()) - def __repr__(self): - return self.__str__() + return str(self.to_soap_object()) class Account(Entity): @@ -217,7 +214,7 @@ class AddressKey(Entity): SOAP_TYPE = 'ns1:AddressKeyType' def __init__(self, client, key, address): - super().__init__(self.SOAP_TYPE, client, self.SOAP_MAP) + super().__init__(self.SOAP_TYPE, client.addressing_client, self.SOAP_MAP) self.key = key self.address = address @@ -321,6 +318,7 @@ class Collection(Entity): def __init__(self, client, collection_id=None, document_id=None, collection_type=None, date=None, sender_address=None, collection_courier=None, labels_url=None, manifest_url=None): super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) + self.despatchbay_client = client self.collection_id = collection_id self.document_id = document_id self.collection_type = collection_type @@ -343,20 +341,32 @@ def from_dict(cls, client, soap_dict): collection_type=soap_dict.get('CollectionType'), date=CollectionDate.from_dict( client, - client.dict(soap_dict.get('CollectionDate')) + client.shipping_client.dict(soap_dict.get('CollectionDate')) ), sender_address=Sender.from_dict( client, - client.dict(soap_dict.get('SenderAddress', None)) + client.shipping_client.dict(soap_dict.get('SenderAddress', None)) ), collection_courier=Courier.from_dict( client, - client.dict(soap_dict.get('Courier', None)) + client.shipping_client.dict(soap_dict.get('Courier', None)) ), labels_url=soap_dict.get('LabelsURL', None), manifest_url=soap_dict.get('Manifest', None) ) + def get_labels(self, **kwargs): + """ + Fetches label pdf through the Despatch Bay API client. + """ + return self.despatchbay_client.fetch_shipment_labels(self.document_id, **kwargs) + + def get_manifest(self, **kwargs): + """ + Fetches menifest pdf through the Despatch Bay API client. + """ + return self.despatchbay_client.fetch_manifest(self.document_id, **kwargs) + class CollectionDate(Entity): """ @@ -465,7 +475,6 @@ class Parcel(Entity): def __init__(self, client, weight=None, length=None, width=None, height=None, contents=None, value=None, tracking_number=None): super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) - self.client = client self.weight = weight self.length = length self.width = width @@ -763,7 +772,7 @@ class ShipmentRequest(Entity): def __init__(self, client, service_id=None, parcels=None, client_reference=None, collection_date=None, sender_address=None, recipient_address=None, follow_shipment=None): super().__init__(self.SOAP_TYPE, client.shipping_client, self.SOAP_MAP) - self._despatchbay_client = client + self.despatchbay_client = client self.service_id = service_id self.parcels = parcels self.client_reference = client_reference @@ -777,7 +786,7 @@ def validate_collection_date_object(self, collection_date): Converts a string timestamp to a CollectionDate object. """ if isinstance(collection_date, str): - return CollectionDate(self._despatchbay_client, date=collection_date) + return CollectionDate(self.despatchbay_client, date=collection_date) return collection_date @property @@ -920,6 +929,18 @@ def from_dict(cls, client, soap_dict): labels_url=soap_dict.get('LabelsURL', None) ) + def book(self): + """ + Makes a BookShipment request through the Despatch Bay API client. + """ + book_return = self.despatchbay_client.book_shipments([self.shipment_id]) + if book_return: + self.shipment_document_id = book_return[0].shipment_document_id + self.collection_id = book_return[0].collection_id + self.collection_document_id = book_return[0].collection_document_id + self.labels_url = book_return[0].labels_url + return book_return[0] + def cancel(self): """ Makes a CancelShipment request through the Despatch Bay API client. From 80dbaee3e087c5cf204222c5f27da69a76e48efc Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Thu, 4 Jul 2019 11:05:47 +0100 Subject: [PATCH 37/41] License --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..965a418 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 The SaleGroup Ltd. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 4355fc755429f65d085a16403637ead15e06294f Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Thu, 4 Jul 2019 16:59:58 +0100 Subject: [PATCH 38/41] setup.py using implicit package names --- setup.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index f11f41d..dcf7683 100644 --- a/setup.py +++ b/setup.py @@ -3,19 +3,17 @@ with open("README.md", "r") as fh: long_description = fh.read() -print(setuptools.find_packages()) - setuptools.setup( name="despatchbay", - version="0.0.5", + version="0.5.3", author="Despatch Bay", author_email="scott.keenan@thesalegroup.co.uk", description="Python SDK for the Despatch Bay API v15", install_requires=['suds-py3', 'requests'], long_description=long_description, long_description_content_type="text/markdown", - url="", - packages=setuptools.find_namespace_packages(include=['despatchbay-python-sdk.despatchbay']), + url="https://test.pypi.org/project/despatchbay/", + packages=setuptools.find_namespace_packages(include=['despatchbay']), classifiers=[ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", From 11a1e1a47e3cd15feea001da7444644f660c14d9 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Mon, 8 Jul 2019 11:24:32 +0100 Subject: [PATCH 39/41] Document printing from collections and shipments --- despatchbay/despatchbay_sdk.py | 12 +++++++----- despatchbay/documents_client.py | 11 ++++++----- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/despatchbay/despatchbay_sdk.py b/despatchbay/despatchbay_sdk.py index 10856e3..95e31d5 100644 --- a/despatchbay/despatchbay_sdk.py +++ b/despatchbay/despatchbay_sdk.py @@ -9,6 +9,8 @@ import suds from . import despatchbay_entities, documents_client, exceptions +MIN_SOAP_API_VERSION = 15 +MIN_DOCUMENTS_API_VERSION = 1 def handle_suds_fault(error): """ @@ -65,10 +67,10 @@ class DespatchBaySDK: https://github.com/despatchbay/despatchbay-api-v15/wiki """ - def __init__(self, api_user, api_key, api_domain='api.despatchbay.com', despactchbay_api_version=15, documents_api_version=1): - if despactchbay_api_version < 15: + def __init__(self, api_user, api_key, api_domain='api.despatchbay.com', despactchbay_api_version=MIN_SOAP_API_VERSION, documents_api_version=MIN_DOCUMENTS_API_VERSION): + if despactchbay_api_version < MIN_SOAP_API_VERSION: raise exceptions.ApiException("DespatchBay API version must be 15 or higher.") - if documents_api_version < 1: + if documents_api_version < MIN_DOCUMENTS_API_VERSION: raise exceptions.ApiException("Documents API version must be 1 or higher.") soap_url_template = 'http://{}/soap/v{}/{}?wsdl' documents_url = 'http://{}/documents/v{}'.format(api_domain, documents_api_version) @@ -365,11 +367,11 @@ def get_tracking(self, tracking_number): # Documents services - def fetch_shipment_labels(self, shipment_document_id, **kwargs): + def fetch_shipment_labels(self, document_ids, **kwargs): """ Fetches labels from the Despatch Bay documents API. """ - return self.pdf_client.fetch_shipment_labels(shipment_document_id, **kwargs) + return self.pdf_client.fetch_shipment_labels(document_ids, **kwargs) def fetch_manifest(self, collection_document_id, **kwargs): """ diff --git a/despatchbay/documents_client.py b/despatchbay/documents_client.py index a24aa86..f0e0a29 100644 --- a/despatchbay/documents_client.py +++ b/despatchbay/documents_client.py @@ -68,14 +68,15 @@ def handle_response_code(code, response): raise exceptions.ApiException('Unknown shipment ID') raise exceptions.ApiException('An unexpected error occurred (HTTP {})'.format(code)) - def fetch_shipment_labels(self, shipment_document_ids, label_layout=None, label_format=None, label_dpi=None): + def fetch_shipment_labels(self, document_ids, label_layout=None, label_format=None, label_dpi=None): """ - Returns a document entity of the shipment labels identified by ship_collect_ids. + Returns a document entity of the shipment labels identified by document_ids which can be a comma + separated string of shipment IDs. """ - if isinstance(shipment_document_ids, list): - shipment_string = ','.join(shipment_document_ids) + if isinstance(document_ids, list): + shipment_string = ','.join(document_ids) else: - shipment_string = shipment_document_ids + shipment_string = document_ids query_dict = {} if label_layout: query_dict['layout'] = label_layout From 3567da3423d793cd7e0ce911fb57a6055a50ca91 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Mon, 8 Jul 2019 12:04:16 +0100 Subject: [PATCH 40/41] renamed labels and documentsmethods. --- despatchbay/despatchbay_entities.py | 6 +++--- despatchbay/despatchbay_sdk.py | 8 ++++---- despatchbay/documents_client.py | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/despatchbay/despatchbay_entities.py b/despatchbay/despatchbay_entities.py index 939df43..0825c57 100644 --- a/despatchbay/despatchbay_entities.py +++ b/despatchbay/despatchbay_entities.py @@ -359,13 +359,13 @@ def get_labels(self, **kwargs): """ Fetches label pdf through the Despatch Bay API client. """ - return self.despatchbay_client.fetch_shipment_labels(self.document_id, **kwargs) + return self.despatchbay_client.get_labels(self.document_id, **kwargs) def get_manifest(self, **kwargs): """ Fetches menifest pdf through the Despatch Bay API client. """ - return self.despatchbay_client.fetch_manifest(self.document_id, **kwargs) + return self.despatchbay_client.get_manifest(self.document_id, **kwargs) class CollectionDate(Entity): @@ -954,4 +954,4 @@ def get_labels(self, **kwargs): """ Fetches label pdf through the Despatch Bay API client. """ - return self.despatchbay_client.fetch_shipment_labels(self.shipment_document_id, **kwargs) + return self.despatchbay_client.get_labels(self.shipment_document_id, **kwargs) diff --git a/despatchbay/despatchbay_sdk.py b/despatchbay/despatchbay_sdk.py index 95e31d5..a1a9e24 100644 --- a/despatchbay/despatchbay_sdk.py +++ b/despatchbay/despatchbay_sdk.py @@ -367,14 +367,14 @@ def get_tracking(self, tracking_number): # Documents services - def fetch_shipment_labels(self, document_ids, **kwargs): + def get_labels(self, document_ids, **kwargs): """ Fetches labels from the Despatch Bay documents API. """ - return self.pdf_client.fetch_shipment_labels(document_ids, **kwargs) + return self.pdf_client.get_labels(document_ids, **kwargs) - def fetch_manifest(self, collection_document_id, **kwargs): + def get_manifest(self, collection_document_id, **kwargs): """ Fetches manifests from the Despatch Bay documents API. """ - return self.pdf_client.fetch_manifest(collection_document_id, **kwargs) + return self.pdf_client.get_manifest(collection_document_id, **kwargs) diff --git a/despatchbay/documents_client.py b/despatchbay/documents_client.py index f0e0a29..8825345 100644 --- a/despatchbay/documents_client.py +++ b/despatchbay/documents_client.py @@ -68,10 +68,10 @@ def handle_response_code(code, response): raise exceptions.ApiException('Unknown shipment ID') raise exceptions.ApiException('An unexpected error occurred (HTTP {})'.format(code)) - def fetch_shipment_labels(self, document_ids, label_layout=None, label_format=None, label_dpi=None): + def get_labels(self, document_ids, label_layout=None, label_format=None, label_dpi=None): """ - Returns a document entity of the shipment labels identified by document_ids which can be a comma - separated string of shipment IDs. + Returns a document entity of the shipment labels identified by the document_ids arg + which can be a comma separated string of shipment IDs or a single collection ID. """ if isinstance(document_ids, list): shipment_string = ','.join(document_ids) @@ -93,9 +93,9 @@ def fetch_shipment_labels(self, document_ids, label_layout=None, label_format=No self.handle_response_code(response.status_code, response.text) return Document(response.content) - def fetch_manifest(self, collection_document_id, manifest_format=None): + def get_manifest(self, collection_document_id, manifest_format=None): """ - Returns a document entity of the shipment manifest identified by collection_id. + Returns a document entity of the shipment manifest identified by collection_document_id. """ manifest_request_url = '{}/manifest/{}'.format(self.api_url, collection_document_id) if manifest_format: From c4d2a3b6b6c323e0176bb8f06ada38f89570e433 Mon Sep 17 00:00:00 2001 From: Scott Keenan Date: Mon, 8 Jul 2019 12:07:13 +0100 Subject: [PATCH 41/41] Demo credentials in example --- examples/shipping_and_documents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shipping_and_documents.py b/examples/shipping_and_documents.py index 386f0ad..3bee718 100644 --- a/examples/shipping_and_documents.py +++ b/examples/shipping_and_documents.py @@ -59,5 +59,5 @@ added_shipment = client.add_shipment(shipment_request) client.book_shipments([added_shipment]) shipment_return = client.get_shipment(added_shipment) -label_pdf = client.fetch_shipment_labels(shipment_return.shipment_document_id) +label_pdf = client.get_labels(shipment_return.shipment_document_id) label_pdf.download('./new_label.pdf')