1
votes

I have been successfully using the Salesforce REST API for a while, but have encountered some functionality that is only available via the SOAP API. One such function is the convertLead() function. I believe that I should be able to use the Salesforce Access Token (a/k/a Session ID) to call the SOAP API but I don't see a good library to use for this function as well as a few others that I'm interested in.

Salesforce provides a sample SOAP request here: https://developer.salesforce.com/page/Enterprise_Convert_Lead

I have attempted to write some the convertLead() function but running into some errors, and since I'm new to SOAP as well as the lxml library, I'm not sure how to diagnose.

Here is the class that I've written to generate the XML:

from lxml import etree


class SalesforceLeadConverter(object):

    def __init__(self, session_id, lead_id, **kwargs):
        self.session_id = session_id
        self.lead_id = lead_id

    def build_xml(self):
        root = etree.Element(
            "{soapenv}Envelope",
             soapenv='<a rel="nofollow" class="external free" href="http://schemas.xmlsoap.org/soap/envelope/">http://schemas.xmlsoap.org/soap/envelope/</a>',
            urn="urn:enterprise.soap.sforce.com"
            )
        soapenv = etree.SubElement(root, "{soapenv}Header")
        urn = etree.SubElement(soapenv, "{urn}SessionHeader")
        session_id = etree.SubElement(urn, "{urn}sessionId").text=self.session_id
        soapenv2 = etree.SubElement(root, "{soapenv}Body")
        urn2 = etree.SubElement(soapenv2, "{urn}convertLead")
        lead_converts = etree.SubElement(urn2, "{urn}leadConverts")
        lead_id = etree.SubElement(lead_converts, "{urn}leadId").text=self.lead_id

        print(etree.tostring(root, pretty_print=True))
        return root

This is designed so that I can pass in a value for lead_id and session_id (access token) and generate some XML, like so:

 >>> from integrations.salesforce.soap import SalesforceLeadConverter
 >>> slc = SalesforceLeadConverter(session_id="1q2w3e4r...", lead_id="00Qj000000PMV3h")
 >>> xml = slc.build_xml()

Which generates the following output:

 <ns0:Envelope xmlns:ns0="soapenv" urn="urn:enterprise.soap.sforce.com">
    <ns0:Header>
      <ns1:SessionHeader xmlns:ns1="urn">
        <ns1:sessionId>1q2w3e4r...</ns1:sessionId>
      </ns1:SessionHeader>
    </ns0:Header>
    <ns0:Body>
      <ns2:convertLead xmlns:ns2="urn">
        <ns2:leadConverts>
          <ns2:leadId>00Qj000000PMV3h</ns2:leadId>
        </ns2:leadConverts>
      </ns2:convertLead>
    </ns0:Body>
  </ns0:Envelope>

Finally, I pass the XML data to the Salesforce SOAP API endpoint using requests; I set 'SOAPAction' to any value (in this case 'x') because of another thread I found that recommended setting this to some random text value.

>>> headers = {'Content-Type':'application/xml', 'SOAPAction':'x'}
>>> requests.post('https://na1.salesforce.com/services/Soap/c/10.0', data=xml, headers=headers)

I'm getting the following error:

TypeError: data must be a memoryview, buffer or byte string

Full error output is here: https://gist.github.com/joefusaro/f3297c253beb6bba93e075d8b8da9822

1

1 Answers

2
votes

You are passing the root which is an Element object, you need to return what you are printing i.e etree.tostring(root):

def build_xml(self):
        root = etree.Element(
            "{soapenv}Envelope",
            urn="urn:enterprise.soap.sforce.com"
            )
        soapenv = etree.SubElement(root, "{soapenv}Header")
        urn = etree.SubElement(soapenv, "{urn}SessionHeader")
        session_id = etree.SubElement(urn, "{urn}sessionId").text=self.session_id
        soapenv2 = etree.SubElement(root, "{soapenv}Body")
        urn2 = etree.SubElement(soapenv2, "{urn}convertLead")
        lead_converts = etree.SubElement(urn2, "{urn}leadConverts")
        lead_id = etree.SubElement(lead_converts, "{urn}leadId").text=self.lead_id
        return etree.tostring(root)

You may also want to change your headers = {'Content-Type':'text/xml'}