I'm trying to use the API from DHL to add shipment details and get a label in return. I'm using the soapbox with SoapUI and be able to make requests. Now I want to do this in Python. I'm not really sure about the steps. I stumbled across this: DHL Soap Request Python
Can somebody help me with getting this to run? The authentication is working but I don't know how to build the soap request.
First trial was to use the SoapUI Software for doing some simple requests or to test everything. With my credentials I was able to send a request and get a label in return.
As endpoint the following URL was used, taken from the doc: https://cig.dhl.de/services/sandbox/soap
The XML looks like this:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:cis="http://dhl.de/webservice/cisbase" xmlns:ns="http://dhl.de/webservices/businesscustomershipping/3.0">
<soapenv:Header>
<cis:Authentification>
<cis:user>2222222222_01</cis:user>
<cis:signature>pass</cis:signature>
</cis:Authentification>
</soapenv:Header>
<soapenv:Body>
<ns:CreateShipmentOrderRequest>
<ns:Version>
<majorRelease>3</majorRelease>
<minorRelease>1</minorRelease>
</ns:Version>
<ShipmentOrder>
<sequenceNumber></sequenceNumber>
<Shipment>
<ShipmentDetails>
<product>V62WP</product>
<cis:accountNumber>${#Project#testAccountNumberV62WP}</cis:accountNumber>
<customerReference>123456</customerReference>
<shipmentDate>2021-09-03</shipmentDate>
<costCentre></costCentre>
<ShipmentItem>
<weightInKG>1</weightInKG>
<lengthInCM>25</lengthInCM>
<widthInCM>15</widthInCM>
<heightInCM>1</heightInCM>
</ShipmentItem>
<Service>
</Service>
<Notification>
<recipientEmailAddress>empfaenger@test.de</recipientEmailAddress>
</Notification>
</ShipmentDetails>
<Shipper>
<Name>
<cis:name1>Absender Zeile 1</cis:name1>
<cis:name2>Absender Zeile 2</cis:name2>
<cis:name3>Absender Zeile 3</cis:name3>
</Name>
<Address>
<cis:streetName>Vegesacker Heerstr.</cis:streetName>
<cis:streetNumber>111</cis:streetNumber>
<cis:zip>28757</cis:zip>
<cis:city>Bremen</cis:city>
<cis:Origin>
<cis:country></cis:country>
<cis:countryISOCode>DE</cis:countryISOCode>
</cis:Origin>
</Address>
<Communication>
<!--Optional:-->
<cis:phone>+49421987654321</cis:phone>
<cis:email>absender@test.de</cis:email>
<!--Optional:-->
<cis:contactPerson>Kontaktperson Absender</cis:contactPerson>
</Communication>
</Shipper>
<Receiver>
<cis:name1>Name</cis:name1>
<Address>
<cis:name2>Empfänger Zeile 2</cis:name2>
<cis:name3>Empfänger Zeile 3</cis:name3>
<cis:streetName>Street</cis:streetName>
<cis:streetNumber>Number</cis:streetNumber>
<cis:zip>zipCode</cis:zip>
<cis:city>City</cis:city>
<cis:Origin>
<cis:country></cis:country>
<cis:countryISOCode>DE</cis:countryISOCode>
</cis:Origin>
</Address>
<Communication>
<cis:phone>+49421123456789</cis:phone>
<cis:email>empfaenger@test.de</cis:email>
<cis:contactPerson>Kontaktperson Empfänger</cis:contactPerson>
</Communication>
</Receiver>
</Shipment>
<PrintOnlyIfCodeable active="1"/>
</ShipmentOrder>
<labelResponseType>URL</labelResponseType>
<groupProfileName></groupProfileName>
<labelFormat></labelFormat>
<labelFormatRetoure></labelFormatRetoure>
<combinedPrinting>0</combinedPrinting>
</ns:CreateShipmentOrderRequest>
</soapenv:Body>
</soapenv:Envelope>
My Python code looks like this:
from requests import Session
from requests.auth import HTTPBasicAuth
from zeep import Client, xsd
from zeep.transports import Transport
user = "my_user_name"
password = "my_password"
USER = "2222222222_01" #from doc
PASSWORD = "pass" #from doc
EKP = "2222222222"
wsdl = "./geschaeftskundenversand-api-3.1.8.wsdl" #downloaded and stored local
session = Session()
# Authenticate with gateway
session.auth = HTTPBasicAuth(user, password)
client = Client(wsdl, transport=Transport(session=session))
# Build Authentification header for API-Endpoint using zeep xsd
header = xsd.Element(
'{http://test.python-zeep.org}Authentification',
xsd.ComplexType([
xsd.Element(
'{http://test.python-zeep.org}user',
xsd.String()),
xsd.Element(
'{http://test.python-zeep.org}signature',
xsd.String()),
])
)
header_value = header(user = USER, signature = PASSWORD)
client.service.createShipmentOrder(_soapheaders=[header_value])
As a result I'm getting a traceback: Missing Element Version
It seems to me that the authentication is working and now I need to insert the body parts of the XML. What I understood, that the next step should be:
client.service.createShipmentOrder
and here I could pass a dictionary for example. createShipmentOrder is one of the requests as stated in the documentary. It gives back a XML with Status OK, a shipment number and a URL to the label for printing. In my opinion now I have to pass the address and so on as stated in the XML. In the SoupUI the request is send to the endpoint URL https://cig.dhl.de/services/sandbox/soap. But in my Python Code the Endpoint isn't stated. Do I need it or is it part of the wsdl file I stored locally?
So as nobody was able to help me, I did it myself :)
I needed some hours to understand the API but now it is clear...
You can use mostly of the code from my question. You have to generate a nested dictionary with all the needed data. For Example:
dict = {'Version':{'majorRelease' : '3', 'minorRelease' : '1'},
'ShipmentOrder':
{'sequenceNumber':'','Shipment':
{'ShipmentDetails':
{'product':product, 'accountNumber':accountNumber, 'customerReference':customerReference,
'shipmentDate':shipmentDate,
'ShipmentItem':{'weightInKG':'0.9', 'lengthInCM':'35', 'widthInCM':'24', 'heightInCM':'5'}
},
'Shipper':{
'Name':{'name1':'your name'},
'Address':{'streetName':'your street','streetNumber':'1234', 'zip':'11111', city':'New York',
'Origin':{'country':'', 'countryISOCode':'DE'}}
},
'Receiver':{
'name1':receiver_name,
'Address':{'streetName':receiver_street,'streetNumber':receiver_streetnumber, 'zip':receiver_zip, 'city':receiver_city,
'Origin':{'country':'', 'countryISOCode':'DE'}}
}
}
},
'labelFormat':label_groesse
}
save the wsdl files into a folder and link your script to it like:
wsdl = "./geschaeftskundenversand-api-3.1.8.wsdl"
You need a header for the request and the authentication at the gateway. The authentication is run over a simple request as shown already in the question.
All you need now is the soap request:
response = client.service.createShipmentOrder(_soapheaders=[header_value], **dict)
where you put in the header and the dict. If you run everything then you will get a response which says 'OK' and has a shipment number and the url to your label in it.
That's it.
I'm running the script with some streamlit parts on a Raspberry Pi4 and let the label printer print everything. Here my code for printing the label:
import subprocess
conn = cups.Connection() #Connection to CUPS
printers = conn.getPrinters() #Get all listed printers
use_printer = printers['QL-1110NWB']["device-uri"] #choose the one label printer
file = requests.get(label_url) #Save the label from url into pdf document locally
with open('label.pdf', 'wb') as pdf:
pdf.write(file.content)
subprocess.run(['lp', '-d', 'QL-1110NWB', '-o fit-to-page', 'label.pdf']) # print the saved label