[SOLVED (uglily)] Consuming OTRS web services with python ZSI

Moderator: crythias

Locked
StCyr
Znuny newbie
Posts: 13
Joined: 25 Feb 2014, 16:32
Znuny Version: 4.something

[SOLVED (uglily)] Consuming OTRS web services with python ZSI

Post by StCyr »

Hi,

I'm trying to consume OTRS web services with python ZSI.

ZSI has a nice tool to autiomatically generate a SOAP client from a wsdl file.

So, a simple "wsdl2py --complexType GenericTicketConnectorSOAP.wsdl" generates a nice GenericTicketConnectorSOAP_client class that's pretty straightforward to use.

Example:

Code: Select all

from GenericTicketConnectorSOAP_client import *
  loc = GenericTicketConnectorLocator()
  port = loc.getGenericTicketConnector_Port()
  req = TicketSearchRequest()
  req.UserLogin = 'read-only-soap-user'
  req.Password = 'xxxxxxx'
  req.StateType = ['open','new','pending auto', 'pending reminder']
  resp = port.TicketSearch(req)
and, that's it: resp contains an array with all the TicketID matching the search criteria

Unfortunatelly, It seems there's an issue with DynamicFields: According to the documentation, dynamic fields should be used as follow:
# DynamicFields
# At least one operator must be specified. Operators will be connected with AND,
# values in an operator with OR.
# You can also pass more than one argument to an operator: ['value1', 'value2']
DynamicField_FieldNameX => {
Equals => 123,
Like => 'value*', # "equals" operator with wildcard support
GreaterThan => '2001-01-01 01:01:01',
GreaterThanEquals => '2001-01-01 01:01:01',
SmallerThan => '2002-02-02 02:02:02',
SmallerThanEquals => '2002-02-02 02:02:02',
}
And, I don't understand how I'm supposed to code that in python.

In fact, I' doubt it possible at all. For example doing "req.DynamicField_EventTicketStartTime.Equals = '2015-12-01 08:59:00'"" results in the following error:

Code: Select all

>>> req.DynamicField_EventTicketStartTime.Equals = '2015-12-01 08:59:00'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'TicketSearch_Holder' object has no attribute 'DynamicField_EventTicketStartTime'
>>> print req.DynamicField
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'TicketSearch_Holder' object has no attribute 'DynamicField'
>>> 
Does any1 know how to achieve this?

Cyrille
Last edited by StCyr on 03 Dec 2015, 10:43, edited 1 time in total.
crythias
Moderator
Posts: 10170
Joined: 04 May 2010, 18:38
Znuny Version: 5.0.x
Location: SouthWest Florida, USA
Contact:

Re: Consuming OTRS web services with python ZSI

Post by crythias »

First, please note that support for less than OTRS 3.3 is going to be sparse, if at all, on this forum. It is strongly recommended to upgrade to 3.3.latest and then to 4.0.latest.

These errors appear to be more python related than OTRS.

How is AttributeError: 'TicketSearch_Holder' object defined in your code?
For reference (anyone who's following along, asking where is this documented?)
https://otrs.github.io/doc/manual/admin ... cketsearch
https://github.com/OTRS/otrs/blob/rel-3 ... ch.pm#L150
OTRS 6.0.x (private/testing/public) on Linux with MySQL database.
Please edit your signature to include your OTRS version, Operating System, and database type.
Click Subscribe Topic below to get notifications. Consider amending your topic title to include [SOLVED] if it is so.
Need help? Before you ask
StCyr
Znuny newbie
Posts: 13
Joined: 25 Feb 2014, 16:32
Znuny Version: 4.something

Re: Consuming OTRS web services with python ZSI

Post by StCyr »

Hi crythias,

thanks for your answer.

I've updated my profile: We are using OTRS 4.x (and OTRS3.3.9+ITSM on another instance) here.

The thing is that my python code is automatically generated from OTRS' wsdl file.

So, either there's a problem in python-zsi's wsdl2py program, or there's a problem in OTRS' wsdl file.

At first sight, I'm suspecting the way DynamicFields are implemented isn't compatible with WSDL, or something like that. I've posted a topic on the developer's forum on this subject (viewtopic.php?f=64&t=30883)

Should it be the case, then python-zsi won't ever be able to produce a proper soap client class. And, I would probably have to hack it to make it compatible with OTRS' soap web services.

Since this is no trivial work, I was first looking for someone with experience in this subject (all one can find on the internet are perl examples, no python).

Best regards,

Cyrille
StCyr
Znuny newbie
Posts: 13
Joined: 25 Feb 2014, 16:32
Znuny Version: 4.something

[SOLVED (uglily) Re: Consuming OTRS web services with python ZSI

Post by StCyr »

So, I've solved my issue and I'm posting here the solution should someone else be willing to consume OTRS web services with python's ZSI framework.

Though I believe the issue originates from OTRS' implementation of Dynamic Fields in their SOAP web services, I've hacked the resulting python's code, because, hacking OTRS would probably be much more difficult.

My hack allows consuming OTRS (SOAP) web services in the following way (example):

Code: Select all

from GenericTicketConnectorSOAP_client import *
from datetime import date
  loc = GenericTicketConnectorLocator()
  port = loc.getGenericTicketConnector_Port()
  req = TicketSearchRequest()
  req.UserLogin = 'agent'
  req.Password = 'password'
  req.StateType = ['open','new','pending auto', 'pending reminder']
  today = date.today()
  # Today's event tickets are those whose start date is no later than today 23:59:59 and end date no sooner than today 00:00:01
  start = req.new_DynamicField_EventTicketStartTime()
  start.SmallerThanEquals = today.isoformat() + " 23:59:59"
  req.DynamicField_EventTicketStartTime = start
  end = req.new_DynamicField_EventTicketEndTime()
  end.GreaterThanEquals = today.isoformat() + " 00:00:01"
  req.DynamicField_EventTicketEndTime = end
  resp = port.TicketSearch(req)
I only hacked the TicketSearch method, and only for being able to search on the "EventTicketStartTime" and "EventTicketEndTime" dynamix fields. So, you'll have to do some more modifications if you need other methods or dynamic fields.

Here's the hack:

Code: Select all

diff --git a/GenericTicketConnectorSOAP_types.py b/GenericTicketConnectorSOAP_types.py
index 874788c..3f81694 100644
--- a/GenericTicketConnectorSOAP_types.py
+++ b/GenericTicketConnectorSOAP_types.py
@@ -228,7 +228,7 @@ class ns0:
         type = (schema, "OTRS_TicketGetResponse_Ticket")
         def __init__(self, pname, ofwhat=(), attributes=None, extend=False, restrict=False, **kw):
             ns = ns0.OTRS_TicketGetResponse_Ticket_Def.schema
-            TClist = [ZSI.TCnumbers.IpositiveInteger(pname="Age", aname="_Age", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname="ArchiveFlag", aname="_ArchiveFlag", minOccurs=1, max
+            TClist = [ZSI.TCnumbers.IpositiveInteger(pname="Age", aname="_Age", minOccurs=1, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname="ArchiveFlag", aname="_ArchiveFlag", minOccurs=1, max
             self.attribute_typecode_dict = attributes or {}
             if extend: TClist += ofwhat
             if restrict: TClist = ofwhat
@@ -249,7 +249,8 @@ class ns0:
                     self._Created = None
                     self._CustomerID = None
                     self._CustomerUserID = None
-                    self._DynamicField = []
+                    self._DynamicField_EventTicketStartTime = None
+                    self._DynamicField_EventTicketEndTime = None
                     self._EscalationDestinationDate = None
                     self._EscalationDestinationIn = None
                     self._EscalationDestinationTime = None
@@ -637,7 +638,7 @@ class ns0:
         schema = "http://www.otrs.org/TicketConnector/"
         def __init__(self, **kw):
             ns = ns0.TicketSearch_Dec.schema
-            TClist = [ZSI.TC.String(pname="UserLogin", aname="_UserLogin", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname="SessionID", aname="_SessionID", minOccurs=0, maxOccurs=1,
+            TClist = [ZSI.TC.String(pname="UserLogin", aname="_UserLogin", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname="SessionID", aname="_SessionID", minOccurs=0, maxOccurs=1,
             kw["pname"] = ("http://www.otrs.org/TicketConnector/","TicketSearch")
             kw["aname"] = "_TicketSearch"
             self.attribute_typecode_dict = {}
@@ -684,7 +685,8 @@ class ns0:
                     self._CreatedStateIDs = []
                     self._CreatedQueues = []
                     self._CreatedQueueIDs = []
-                    self._DynamicFields = []
+                    self._DynamicField_EventTicketStartTime = None
+                    self._DynamicField_EventTicketEndTime = None
                     self._Ticketflag = None
                     self._From = None
                     self._To = None
In the hack hereabove, the "TClist = ..." lines are truncated. Here's what I actually changed:

Code: Select all

ZSI.TC.String(pname="DynamicField_EventTicketStartTime", aname="_DynamicField_EventTicketStartTime", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")), ZSI.TC.String(pname="DynamicField_EventTicketEndTime", aname="_DynamicField_EventTicketEndTime", minOccurs=0, maxOccurs=1, nillable=False, typed=False, encoded=kw.get("encoded")),
That's basically all what was needed. But, more generally, there are 2 other changes that one needs to do:

in /usr/lib/pymodules/python2.7/ZSI/client.py, add ' or mimetype == 'application/soap+xml'' to accomodate with OTRS responses' MIME format:

Code: Select all

    def IsSOAP(self):
        if self.ps: return 1
        self.ReceiveRaw()
        mimetype = self.reply_headers.type
        return ( mimetype == 'text/xml' or mimetype == 'application/soap+xml' )
in /usr/lib/pymodules/python2.7/ZSI/TC.py, add 'xsi:' so as to not error on empty OTRS responses:

Code: Select all

_find_nil = lambda E: _find_xsi_attr(E, "null") or _find_xsi_attr(E, "xsi:nil")
Nice day you nice reader
Locked