mvantellingen / python-zeep

A Python SOAP client
http://docs.python-zeep.org
Other
1.9k stars 588 forks source link

BaseRef is an abstract type and cannot be instantiated #37

Closed Kilavagora closed 8 years ago

Kilavagora commented 8 years ago

The following:

from zeep import Client
from zeep.transports import Transport

transport = Transport(verify=False)
wsdl='https://webservices.netsuite.com/wsdl/v2015_1_0/netsuite.wsdl'
client=Client(wsdl,transport=transport)

RecordRef=client.get_type('ns0:RecordRef')

case_ref=RecordRef(internalId='5000000',type='supportCase')

response=client.service.get(case_ref)

gives this error:

zeep.exceptions.Fault: org.xml.sax.SAXException: {urn:core_2015_1.platform.webservices.netsuite.com}BaseRef is an abstract type and cannot be instantiated

Here is the posted payload. It's missing the type specification and the attributes:

<soap-env:Envelope xmlns:ns20="urn:inventory_2015_1.transactions.webservices.netsuite.com" xmlns:ns17="urn:customers_2015_1.transactions.webservices.netsuite.com" xmlns:ns40="urn:types.common_2015_1.platform.webservices.netsuite.com" xmlns:ns26="urn:types.employees_2015_1.lists.webservices.netsuite.com" xmlns:ns23="urn:customization_2015_1.setup.webservices.netsuite.com" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:ns2="urn:messages_2015_1.platform.webservices.netsuite.com" xmlns:ns41="urn:types.purchases_2015_1.transactions.webservices.netsuite.com" xmlns:ns6="urn:communication_2015_1.general.webservices.netsuite.com" xmlns:ns44="urn:types.bank_2015_1.transactions.webservices.netsuite.com" xmlns:ns8="urn:relationships_2015_1.lists.webservices.netsuite.com" xmlns:ns12="urn:accounting_2015_1.lists.webservices.netsuite.com" xmlns:ns37="urn:types.supplychain_2015_1.lists.webservices.netsuite.com" xmlns:ns3="urn:common_2015_1.platform.webservices.netsuite.com" xmlns:ns18="urn:financial_2015_1.transactions.webservices.netsuite.com" xmlns:ns30="urn:types.website_2015_1.lists.webservices.netsuite.com" xmlns:ns43="urn:types.financial_2015_1.transactions.webservices.netsuite.com" xmlns:ns45="urn:types.employees_2015_1.transactions.webservices.netsuite.com" xmlns:ns35="urn:types.demandplanning_2015_1.transactions.webservices.netsuite.com" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:ns31="urn:employees_2015_1.transactions.webservices.netsuite.com" xmlns:ns14="urn:sales_2015_1.transactions.webservices.netsuite.com" xmlns:ns27="urn:filecabinet_2015_1.documents.webservices.netsuite.com" xmlns:ns10="urn:support_2015_1.lists.webservices.netsuite.com" xmlns:ns34="urn:demandplanning_2015_1.transactions.webservices.netsuite.com" xmlns:ns5="urn:types.scheduling_2015_1.activities.webservices.netsuite.com" xmlns:ns24="urn:types.customization_2015_1.setup.webservices.netsuite.com" xmlns:ns29="urn:website_2015_1.lists.webservices.netsuite.com" xmlns:ns32="urn:marketing_2015_1.lists.webservices.netsuite.com" xmlns:ns39="urn:types.faults_2015_1.platform.webservices.netsuite.com" xmlns:ns1="urn:faults_2015_1.platform.webservices.netsuite.com" xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns15="urn:types.sales_2015_1.transactions.webservices.netsuite.com" xmlns:ns16="urn:purchases_2015_1.transactions.webservices.netsuite.com" xmlns:ns22="urn:general_2015_1.transactions.webservices.netsuite.com" xmlns:ns36="urn:supplychain_2015_1.lists.webservices.netsuite.com" xmlns:ns7="urn:types.communication_2015_1.general.webservices.netsuite.com" xmlns:ns4="urn:scheduling_2015_1.activities.webservices.netsuite.com" xmlns:ns38="urn:types.core_2015_1.platform.webservices.netsuite.com" xmlns:ns0="urn:core_2015_1.platform.webservices.netsuite.com" xmlns:ns11="urn:types.support_2015_1.lists.webservices.netsuite.com" xmlns:ns13="urn:types.accounting_2015_1.lists.webservices.netsuite.com" xmlns:ns25="urn:employees_2015_1.lists.webservices.netsuite.com" xmlns:ns33="urn:types.marketing_2015_1.lists.webservices.netsuite.com" xmlns:ns9="urn:types.relationships_2015_1.lists.webservices.netsuite.com" xmlns:ns42="urn:types.customers_2015_1.transactions.webservices.netsuite.com" xmlns:ns21="urn:types.inventory_2015_1.transactions.webservices.netsuite.com" xmlns:ns19="urn:bank_2015_1.transactions.webservices.netsuite.com" xmlns:ns28="urn:types.filecabinet_2015_1.documents.webservices.netsuite.com">
    <soap-env:Header><ns2:passport><ns0:email/><ns0:password/><ns0:account/></ns2:passport></soap-env:Header>
    <soap-env:Body>
        <ns2:get>
            <ns2:baseRef/>
        </ns2:get>
    </soap-env:Body>
</soap-env:Envelope>

Below is the payload generated from C# client that works and gives the correct response:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <soap:Body>
        <get xmlns="urn:messages_2015_1.platform.webservices.netsuite.com">
            <baseRef xmlns:q1="urn:core_2015_1.platform.webservices.netsuite.com" xsi:type="q1:RecordRef" internalId="5000000" type="supportCase" />
        </get>
    </soap:Body>
</soap:Envelope>

P.S. Thanks for fixing the previous issue :)

mvantellingen commented 8 years ago

Oke, so handling subtypes was not implemented correctly (RecordRef is a subtype of BaseRef). This should be fixed now.

You also may need to add the soap header (pass _soapheader to the method as kwarg)

Kilavagora commented 8 years ago

Awesome! It worked. Thank you :) But I believe headers should be optional here, they shouldn’t be included unless you specify them explicitly. It does not work if you skip the header.

mvantellingen commented 8 years ago

See 6ed076a2f15de4c511df67c106f3d5ac9d7d537c

Kilavagora commented 8 years ago

Works without headers! For the service above, you either need to specify authentication information in SOAP header for each request, or authenticate and use the session. So I had to modify Transport to make it work with sessions:

class Transport(object):

    def __init__(self, cache=None, timeout=300, verify=True, http_auth=None, session=False, cookies=None):
        self.cache = cache or SqliteCache()
        self.timeout = timeout
        self.verify = verify
        self.http_auth = http_auth
        self.r=requests.Session() if session else requests
        self.cookies=cookies

    def load(self, url):
        if self.cache:
            response = self.cache.get(url)
            if response:
                return bytes(response)

        response = self.r.get(url, timeout=self.timeout, verify=self.verify,
                                auth=self.http_auth, cookies=self.cookies)

        if self.cache:
            self.cache.add(url, response.content)

        return response.content

    def post(self, address, message, headers):
        response = self.r.post(
            address, data=message, headers=headers, verify=self.verify,
            auth=self.http_auth, cookies=self.cookies
        )
        return response

    def get(self, address, params, headers):
        response = self.r.get(
            address, params=params, headers=headers, verify=self.verify,
            auth=self.http_auth,
            cookies=self.cookies
        )
        return response
mvantellingen commented 8 years ago

Seems like something we should offer by default in zeep. Is there a reason you didn't simply choose for a use_cookies=True/False kwag in the __init__ method?

Kilavagora commented 8 years ago

I don't know whether I have a valid reason, but from what I understood from this, manually set cookies do not persist and they are kept separately from the session.

BTW, rather than adding parameters, wouldn't it be preferable to pass all the rest directly to requests kwargs without manually specifying the parameters? E.g. if there is a service that requires custom HTTP headers, or custom GET parameters, right now there is no direct way to pass these.

mvantellingen commented 8 years ago

I've update the transport class. It should be easier to customize now by subclassing it and override the create_session() method.

Regarding the cookies. Do you set custom cookies based on pre-defined values?

Kilavagora commented 8 years ago

I don't. Just session works fine for me :) But I need to pass custom HTTP headers eventually.

P.S. BTW, transport = Transport(cache=None) does not seem to disable caching.

mvantellingen commented 8 years ago

Was thinking more about this. I think it is currently pretty easy to either create a custom transport class (subclass) or set transport.session.proxies = xx. So i'm closing it for now