Sage CRM servizi web ad esempio in Python
Domanda
Sto cercando di scrivere un consumatore Python per Sage CRM utilizzando l'interfaccia Web Services. Sto usando SOAPpy come libreria SOAP Python (non sposata ad esso, era facile da installare su Ubuntu quindi siamo andati con esso).
Gestito per andare a prendere il WSDL utilizzando il proxy ed eseguito il metodo di accesso esposto da Sage CRM.
from SOAPpy import *
proxy = WSDL.Proxy('http://192.168.0.3/MATE/eware.dll/webservice/webservice.wsdl')
Si restituisce un oggetto sessione che sorta di assomiglia
SOAPpy.Types.structType result at 151924492: {'sessionid': '170911104429792'}
Ora sto cercando di utilizzare il metodo queryrecord di Sage CRM per interrogare i dati, e che i rendimenti
Fault SOAP-ENV:Server: No active usersession detected
Leggendo la documentazione rivela che devo rispedire l'ID di sessione che RECD. quando mi sono collegato indietro con ogni richiesta.
In base alla documentazione Sage devo rimandare indietro come tale
SID = binding.logon("admin", "");
binding.SessionHeaderValue = new SessionHeader();
binding.SessionHeaderValue.sessionId = SID.sessionid;
Tutte le idee su come fare questo accodare alle intestazioni usando SOAPpy?
Tutti gli indicatori sarà molto apprezzato.
Soluzione
In primo luogo, solo per commentare SOAPpy vs. altre librerie di sapone ... SOAPpy tradizionalmente è stata la libreria facile da usare che non supporta strutture dati complesse. Quindi, se funziona per il vostro caso, allora si sta meglio. Per maggiori strutture dati complesse nel WSDL avresti bisogno di muoversi ZSI.
In ogni caso, il collegamento documentazione per il progetto su SourceForge sembra essere rotto, quindi mi limiterò a tagliare + incollare la documentazione intestazione qui con alcune modifiche di formattazione minori:
Uso di intestazioni
SOAPpy ha una classe di intestazione per contenere i dati per l'intestazione di un messaggio SOAP. Ogni istanza Header ha metodi per impostare / ottenere l'attributo MustUnderstand, e metodi per impostare / ottenere l'attributo di attore.
SOAPpy ha anche una classe SOAPContext in modo che ogni metodo di server può essere implementato in modo tale che si ottiene nel contesto del client connesso. Questo include sia le informazioni SOAP comune e le informazioni di connessione (vedi qui di seguito per un esempio).
Esempi client
import SOAPpy
test = 42
server = SOAPpy.SOAPProxy("http://localhost:8888")
server = server._sa ("urn:soapinterop")
hd = SOAPpy.Header()
hd.InteropTestHeader ='This should fault, as you don\'t understand the header.'
hd._setMustUnderstand ('InteropTestHeader', 0)
hd._setActor ('InteropTestHeader','http://schemas.xmlsoap.org/soap/actor/next')
server = server._hd (hd)
print server.echoInteger (test)
Questo dovrebbe riuscire (a condizione che il server ha definito echoInteger), in quanto costruisce un'intestazione valida in questo client con MustUnderstand set a 0 e quindi invia il sapone con questa intestazione.
import SOAPpy
test = 42
server = SOAPpy.SOAPProxy("http://localhost:8888")
server = server._sa ("urn:soapinterop")
#Header
hd = SOAPpy.Header()
hd.InteropTestHeader = 'This should fault,as you don\'t understand the header.'
hd._setMustUnderstand ('InteropTestHeader', 1)
hd._setActor ('InteropTestHeader','http://schemas.xmlsoap.org/soap/actor/next')
server = server._hd (hd)
print server.echoInteger (test)
Questo dovrebbe fallire (anche se il server ha definito 'echoInteger'), in quanto costruisce un'intestazione valida in questo client, ma imposta MustUnderstand a 1 per un messaggio che il server presumibilmente non capirà prima di inviare.
Altri suggerimenti
sto solo immaginando questo troppo. Ho il cuore di questo fatto, però, quindi questo dovrebbe accelerare le cose per chiunque altro che ha bisogno di questo! Io parto dal presupposto altri sottosistemi Sage funzionano allo stesso modo (ma non so ancora), così ho cercato di tenere conto di questa possibilità.
In primo luogo, avete bisogno di questo modulo che ho scritto chiamato pySage.py. Si definisce una classe per l'esecuzione di un processo che è necessario creare una sottoclasse per il vostro uso personalizzato.
#----------------------------
#
# pySage.py
#
# Author: BuvinJ
# Created: December, 2015
#
# This module defines the SageProcess class.
# This class handles connecting and disconnecting
# to Sage web services, and provides an object
# through which the web services can be accessed.
#
#----------------------------
# Download SOAPpy from: https://pypi.python.org/pypi/SOAPpy
from SOAPpy import WSDL, Types as soapTypes
from enum import Enum
SageSubsystem = Enum( 'SageSubsystem', 'CRM' )
# Define your sub system connection parameters here.
CRM_WSDL_FILE_URL = "http://CRMservername/CRMinstallname/eWare.dll/webservice/webservice.wsdl"
CRM_USER = "admin"
CRM_PASSWORD = ""
CRM_NAMESPACE = "http://tempuri.org/type"
#----------------------------
# SageProcess Class
# To use this class:
#
# 1) Create a SageProcess subclass and define the method
# body().
# 2) Instanitate an instance of the subclass, passing a
# SageSubsystem enumeration, e.g. SageSubsystem.CRM.
# 3) Invoke the run() method of the process instance.
# This will in turn invoke body() to execute your
# custom actions.
#
# To access the sage web services, use the "sage" member of
# a SageProcess instance. For help using Sage web service
# objects see:
# https://community.sagecrm.com/developerhelp/default.htm
#
# You may also invoke the sageHelp() method of a SageProcess
# instance to view the top level web service object members.
# Or, recordHelp( sageRecord ) to view the members of the
# various record objects returned by the web services.
#
#----------------------------
class SageProcess():
# Construction & service connection methods
#----------------------------
def __init__( self, subsystem, verbose=False ):
"""
@param subsystem: The Sage subsystem on which to run the process. Ex. SageSubsystem.CRM
@param verbose: If True, details of the SOAP exchange are displayed.
"""
self.subsystem = subsystem
self.verbose = verbose
if self.subsystem == SageSubsystem.CRM :
self.wsdl = CRM_WSDL_FILE_URL
self.namespace = CRM_NAMESPACE
self.username = CRM_USER
self.password = CRM_PASSWORD
else :
self.abort( "Unknown subsystem specified!" )
self.sessionId = None
self.connect()
def connect( self ) :
try :
self.sage = WSDL.Proxy( self.wsdl, namespace=self.namespace )
except :
self.abort( "WSDL failure. This is may be caused by access settings. File url: " + CRM_WSDL_FILE_URL )
if self.verbose : "Connected to web service."
self.soapProxy = self.sage.soapproxy
if self.verbose : self.sageDebug()
# Core process methods
#----------------------------
def run( self ) :
if not self.logOn( self.username, self.password ) :
self.abort( "Log On failed!" )
else :
if self.verbose :
print "Logged On. Session Id: " + str( self.sessionId )
self.appendSessionHeader()
self.body()
if self.logOff() :
if self.verbose : print "Logged Off."
def logOn( self, username, password ) :
try : self.sessionId = self.sage.logon( username, password ).sessionid
except Exception as e:
self.abortOnException( "Log On failure.\n(You may need to enable forced logins.)", e )
return (self.sessionId is not None)
def appendSessionHeader( self ) :
self.soapProxy = self.soapProxy._sa( "urn:sessionid" )
soapHeader = soapTypes.headerType()
soapHeader.SessionHeader = soapTypes.structType( None, "SessionHeader" )
soapHeader.SessionHeader.sessionId = soapTypes.stringType( self.sessionId )
self.soapProxy = self.soapProxy._hd( soapHeader )
self.sage.soapproxy = self.soapProxy
def body( self ) :
"""
You should override this method when you subclass SageProcess.
It will be called after logging on, and will be followed by logging off.
Use self.sage to access the system from within this method.
"""
def logOff( self ) :
success = False
try : success = self.sage.logoff( self.sessionId )
except Exception as e: self.abortOnException( "Log off failure.", e )
return success
# Helper methods
#----------------------------
# Immediately exit the program with an indication of success
def quit( self, msg=None ) :
if msg is not None: print msg
import os
os._exit( 0 )
# Immediately exit the program with an error
def abort( self, msg=None, errorCode=1 ) :
if msg is not None: print msg
print "Process terminated..."
import os
os._exit( errorCode )
# Immediately exit the program with an Exception error
def abortOnException( self, e, msg=None, errorCode=1 ) :
if msg is not None: print msg
print ""
print e
print ""
self.abort( None, errorCode )
def sageDebug( self, enable=True ) :
if enable : self.soapProxy.config.debug = 1
else : self.soapProxy.config.debug = 0
def sageHelp( self ) :
print "\nSage web service methods:\n"
self.sage.show_methods()
def recordHelp( self, record, typeDescr=None ) :
if record is None : return
print ""
description = "record object members:\n"
if typeDescr is not None :
description = typeDescr + " " + description
print description
print dir( record )
print ""
Quindi, aggiungere un client per questa classe. Ecco un esempio che ho creato chiamato fetch_company_data_example.py:
#----------------------------
#
# fetch_company_data_example.py
#
#----------------------------
from pySage import SageProcess, SageSubsystem
def main() :
# Get process parameters from the command line
import sys
try :
companyId = sys.argv[1]
except :
abort( "Usage: " + sys.argv[0] + " companyId [-v] [--verbose]" )
verbose = False
try :
if ( sys.argv[2] == "-v" or
sys.argv[2] == "--verbose" ) :
verbose = True
except : pass
# Create & run the custom Sage process
process = FetchCompanyDataProcess( companyId, verbose )
process.run()
class FetchCompanyDataProcess( SageProcess ):
def __init__( self, companyId, verbose ):
SageProcess.__init__( self, SageSubsystem.CRM, verbose )
self.companyId = companyId
def body( self ):
# Fetch the company record (exiting if no data is returned)
companyRecord = self.getCompanyRecord()
if companyRecord is None: self.quit( "\nNo records found.\n" )
# Uncomment for development help...
#if self.verbose : self.recordHelp( companyRecord, "Company" )
#if self.verbose : self.recordHelp( companyRecord.address.records, "Address" )
# Print some of the company info
print ""
print "Company Id: " + self.companyId
print "Name: " + companyRecord.name
print "Location: " + self.getCompanyLocation( companyRecord )
print ""
def getCompanyRecord( self ) :
try :
queryentity = self.sage.queryentity( self.companyId, "company" )
except Exception as e:
self.abortOnException( "Get Company Record failure.", e )
try : return queryentity.records
except : return None
def getCompanyLocation( self, companyRecord ) :
try :
return (companyRecord.address.records.city + ", " +
companyRecord.address.records.state)
except : return ""
# Entry point
if __name__ == '__main__' : main()