Skip to content
Snippets Groups Projects
Commit 0423e0f5 authored by Robert Habermann's avatar Robert Habermann
Browse files

Merge branch 'add-link-api'

parents fae87338 8be82247
No related branches found
No related tags found
No related merge requests found
......@@ -13,8 +13,14 @@ This project adheres to [Semantic Versioning](http://semver.org/).
0.2.0 - planned first release
-----------------------------
0.1.11 - 2016-11-03
-------------------
- add link api
- add dynamic_field_get access
- add article_get access
0.1.10 - 2016-09-16
------------------
-------------------
- add testing (and *support*) for Python2.6 (default on RHEL6)
0.1.9 - 2016-09-11
......
......@@ -44,7 +44,7 @@ Some of the most notable methods provided are::
* Client.session_create (Use credentials to "log in")
* Client.ticket_create
* Client.ticket_get_by_list (takes a list)
* Client.ticket_get_by_number (takes a string)
* Client.ticket_get_by_id (takes an int)
* Client.ticket_search
* Client.ticket_update
......
......@@ -18,21 +18,45 @@ import datetime
import requests
OPERATION_MAPPING_DEFAULT = {
'SessionCreate': {'RequestMethod': 'POST', 'Route': '/Session'},
'TicketCreate': {'RequestMethod': 'POST', 'Route': '/Ticket'},
'TicketGet': {'RequestMethod': 'GET', 'Route': '/Ticket/:TicketID'},
'TicketGetList': {'RequestMethod': 'GET', 'Route': '/TicketList'},
'TicketSearch': {'RequestMethod': 'GET', 'Route': '/Ticket'},
'TicketUpdate': {'RequestMethod': 'PATCH', 'Route': '/Ticket/:TicketID'}
}
TYPE_MAPPING_DEFAULT = {
'SessionCreate': 'SessionID',
'TicketCreate': 'TicketID',
'TicketGet': 'Ticket',
'TicketGetList': 'Ticket',
'TicketSearch': 'TicketID',
'TicketUpdate': 'TicketID'
'SessionCreate': {'RequestMethod': 'POST',
'Route': '/Session',
'Result': 'SessionID'},
'TicketCreate': {'RequestMethod': 'POST',
'Route': '/Ticket',
'Result': 'TicketID'},
'TicketGet': {'RequestMethod': 'GET',
'Route': '/Ticket/:TicketID',
'Result': 'Ticket'},
'TicketGetList': {'RequestMethod': 'GET',
'Route': '/TicketList',
'Result': 'Ticket'},
'TicketSearch': {'RequestMethod': 'GET',
'Route': '/Ticket',
'Result': 'TicketID'},
'TicketUpdate': {'RequestMethod': 'PATCH',
'Route': '/Ticket/:TicketID',
'Result': 'TicketID'},
'LinkAdd': {'RequestMethod': 'POST',
'Route': '/LinkAdd',
'Result': 'LinkAdd'},
'LinkDelete': {'RequestMethod': 'DELETE',
'Route': '/LinkDelete',
'Result': 'LinkDelete'},
'LinkDeleteAll': {'RequestMethod': 'DELETE',
'Route': '/LinkDeleteAll',
'Result': 'LinkDeleteAll'},
'LinkList': {'RequestMethod': 'GET',
'Route': '/LinkList',
'Result': 'LinkList'},
'PossibleLinkList': {'RequestMethod': 'GET',
'Route': '/PossibleLinkList',
'Result': 'PossibleLinkList'},
'PossibleObjectsList': {'RequestMethod': 'GET',
'Route': '/PossibleObjectsList',
'Result': 'PossibleObject'},
'PossibleTypesList': {'RequestMethod': 'GET',
'Route': '/PossibleTypesList',
'Result': 'PossibleType'}
}
......@@ -676,13 +700,16 @@ class Client(object):
password (str): Password
session_id_file (str): Session ID path on disc, used to persistently store Session ID
session_timeout (int): Session Timeout configured in OTRS (usually 28800 seconds = 8h)
session_validation_ticket_id (int): Ticket ID of an existing ticket - used to perform
several check - e.g. validate log in (defaults to 1)
webservicename_link (str): OTRSGenericInterfaceLinkMechanism REST Web Service Name
proxies (dict): Proxy settings - refer to requests docs for
more information - default to no proxies
https_verify (bool): Should HTTPS certificates be verified (defaults to True)
ca_cert_bundle (str): file path - if specified overrides python/system default for
Root CA bundle that will be used.
operation_map (dict): A dictionary for mapping OTRS operations to HTTP Method and Route.
type_map (dict): A dictionary for mapping OTRS result type to operations.
operation_map (dict): A dictionary for mapping OTRS operations to HTTP Method, Route and
Result string.
"""
def __init__(self,
......@@ -690,14 +717,14 @@ class Client(object):
webservicename=None,
username=None,
password=None,
session_timeout=None,
session_id_file=None,
session_timeout=None,
session_validation_ticket_id=1,
webservicename_link=None,
proxies=None,
https_verify=True,
ca_cert_bundle=None,
operation_map=None,
type_map=None):
operation_map=None):
if not baseurl:
raise ArgumentMissingError("baseurl")
......@@ -707,6 +734,10 @@ class Client(object):
raise ArgumentMissingError("webservicename")
self.webservicename = webservicename
# TODO 2016-30-10 (RH): check Name and Documentation
if not webservicename_link:
self.webservicename_link = "GenericLinkConnectorREST"
if not session_timeout:
self.session_timeout = 28800 # 8 hours is OTRS default
else:
......@@ -742,11 +773,6 @@ class Client(object):
else:
self.operation_map = OPERATION_MAPPING_DEFAULT
if type_map:
self.type_map = type_map
else:
self.type_map = TYPE_MAPPING_DEFAULT
# credentials
self.username = username
self.password = password
......@@ -1182,6 +1208,264 @@ class Client(object):
return self.ticket_update(ticket_id, State=new_state, PendingTime=pt)
"""
GenericInterface::Operation::Link::LinkAdd
* link_add
"""
def link_add(self,
src_object_id,
dst_object_id,
src_object_type="Ticket",
dst_object_type="Ticket",
link_type="Normal",
state="Valid"):
"""link_add
Args:
src_object_id (int): Integer value of source object ID
dst_object_id (int): Integer value of destination object ID
src_object_type (str): Object type of source; e.g. "Ticket", "FAQ"...
(*default: Ticket*)
dst_object_type (str): Object type of destination; e.g. "Ticket", "FAQ"...
(*default: Ticket*)
link_type (str): Type of the link: "Normal" or "ParentChild" (*default: Normal*)
state (str): State of the link (*default: Normal*)
Returns:
**True** or **False**: True if successful, otherwise **False**.
"""
if not self.session_id_store.value:
raise SessionNotCreated("Call session_create() or "
"session_restore_or_set_up_new() first")
self.operation = "LinkAdd"
payload = {
"SessionID": self.session_id_store.value,
"SourceObject": src_object_type,
"SourceKey": int(src_object_id),
"TargetObject": dst_object_type,
"TargetKey": int(dst_object_id),
"Type": link_type,
"State": state
}
return self._parse_and_validate_response(self._send_request(payload))
"""
GenericInterface::Operation::Link::LinkDelete
* link_delete
"""
def link_delete(self,
src_object_id,
dst_object_id,
src_object_type="Ticket",
dst_object_type="Ticket",
link_type="Normal"):
"""link_delete
Args:
src_object_id (int): Integer value of source object ID
src_object_type (str): Object type of source; e.g. "Ticket", "FAQ"...
(*default: Ticket*)
dst_object_id (int): Integer value of source object ID
dst_object_type (str): Object type of source; e.g. "Ticket", "FAQ"...
(*default: Ticket*)
link_type (str): Type of the link: "Normal" or "ParentChild" (*default: Normal*)
Returns:
**True** or **False**: True if successful, otherwise **False**.
"""
if not self.session_id_store.value:
raise SessionNotCreated("Call session_create() or "
"session_restore_or_set_up_new() first")
self.operation = "LinkDelete"
payload = {
"SessionID": self.session_id_store.value,
"Object1": src_object_type,
"Key1": int(src_object_id),
"Object2": dst_object_type,
"Key2": int(dst_object_id),
"Type": link_type
}
return self._parse_and_validate_response(self._send_request(payload))
"""
GenericInterface::Operation::Link::LinkDeleteAll
* link_delete_all
"""
def link_delete_all(self,
object_id,
object_type="Ticket",):
"""link_delete_all
Args:
object_id (int): Integer value of source object ID
object_type (str): Object type of source; e.g. "Ticket", "FAQ"...
(*default: Ticket*)
Returns:
**True** or **False**: True if successful, otherwise **False**.
"""
if not self.session_id_store.value:
raise SessionNotCreated("Call session_create() or "
"session_restore_or_set_up_new() first")
self.operation = "LinkDeleteAll"
payload = {
"SessionID": self.session_id_store.value,
"Object": object_type,
"Key": int(object_id)
}
return self._parse_and_validate_response(self._send_request(payload))
"""
GenericInterface::Operation::Link::LinkList
* link_list
"""
def link_list(self,
src_object_id,
src_object_type="Ticket",
dst_object_type=None,
state="Valid",
link_type=None,
direction=None):
"""link_list
Args:
src_object_id (int): Object type of source; e.g. "Ticket", "FAQ"...
(*default: Ticket*)
src_object_type (str): Object type of destination; e.g. "Ticket", "FAQ"...
(*default: Ticket*)
dst_object_type (str): Object type of destination; e.g. "Ticket", "FAQ"...
Optional restriction of the object where the links point to. (*default: Ticket*)
state (str): State of the link (*default: Valid*)
link_type (str): Type of the link: "Normal" or "ParentChild" (*default: Normal*)
direction (str): Optional restriction of the link direction ('Source' or 'Target').
Returns:
**Dict** or **None**: Dict if successful, if empty **None**.
"""
if not self.session_id_store.value:
raise SessionNotCreated("Call session_create() or "
"session_restore_or_set_up_new() first")
self.operation = "LinkList"
payload = {
"SessionID": self.session_id_store.value,
"Object": src_object_type,
"Key": int(src_object_id),
"State": state
}
if dst_object_type:
payload.update({"Object2": dst_object_type})
if link_type:
payload.update({"Type": link_type})
if direction:
payload.update({"Direction": direction})
return self._parse_and_validate_response(self._send_request(payload))
"""
GenericInterface::Operation::Link::PossibleLinkList
* link_possible_link_list
"""
def link_possible_link_list(self):
"""link_possible_link_list
Returns:
**List** or **False**: List if successful, otherwise **False**.
"""
if not self.session_id_store.value:
raise SessionNotCreated("Call session_create() or "
"session_restore_or_set_up_new() first")
self.operation = "PossibleLinkList"
payload = {
"SessionID": self.session_id_store.value,
}
if self._parse_and_validate_response(self._send_request(payload)):
return self.result
else:
return False
"""
GenericInterface::Operation::Link::PossibleObjectsList
* link_possible_objects_list
"""
def link_possible_objects_list(self,
object_type="Ticket"):
"""link_possible_objects_list
Args:
object_type (str): Object type; e.g. "Ticket", "FAQ"...
(*default: Ticket*)
Returns:
**List** or **False**: List if successful, otherwise **False**.
"""
if not self.session_id_store.value:
raise SessionNotCreated("Call session_create() or "
"session_restore_or_set_up_new() first")
self.operation = "PossibleObjectsList"
payload = {
"SessionID": self.session_id_store.value,
"Object": object_type,
}
if self._parse_and_validate_response(self._send_request(payload)):
return self.result
else:
return False
"""
GenericInterface::Operation::Link::PossibleTypesList
* link_possible_types_list
"""
def link_possible_types_list(self,
src_object_type="Ticket",
dst_object_type="Ticket"):
"""link_possible_types_list
Args:
src_object_type (str): Object type of source; e.g. "Ticket", "FAQ"...
(*default: Ticket*)
dst_object_type (str): Object type of destination; e.g. "Ticket", "FAQ"...
(*default: Ticket*)
Returns:
**List** or **False**: List if successful, otherwise **False**.
"""
if not self.session_id_store.value:
raise SessionNotCreated("Call session_create() or "
"session_restore_or_set_up_new() first")
self.operation = "PossibleTypesList"
payload = {
"SessionID": self.session_id_store.value,
"Object1": src_object_type,
"Object2": dst_object_type,
}
if self._parse_and_validate_response(self._send_request(payload)):
return self.result
else:
return False
def _build_url(self, ticket_id=None):
"""build url for request
......@@ -1194,7 +1478,7 @@ class Client(object):
"""
route = self.operation_map[self.operation]["Route"]
if not (route.startswith("/Ticket") or route.startswith("/Session")):
if not (route.startswith(("/Ticket", "/Session", "/Possible", "/Link"))):
raise ValueError("Route misconfigured: {0}".format(route))
if ":" in route:
......@@ -1207,16 +1491,29 @@ class Client(object):
raise ValueError("TicketID is None but Route requires "
"TicketID: {0}".format(route))
self._url = ("{0}/otrs/nph-genericinterface.pl/Webservice/"
"{1}{2}{3}".format(self.baseurl,
self.webservicename,
route,
ticket_id))
if route.startswith(("/Link", "/Possible")):
self._url = ("{0}/otrs/nph-genericinterface.pl/Webservice/"
"{1}{2}{3}".format(self.baseurl,
self.webservicename_link,
route,
ticket_id))
else:
self._url = ("{0}/otrs/nph-genericinterface.pl/Webservice/"
"{1}{2}{3}".format(self.baseurl,
self.webservicename,
route,
ticket_id))
else:
self._url = ("{0}/otrs/nph-genericinterface.pl/Webservice/"
"{1}{2}".format(self.baseurl,
self.webservicename,
route))
if route.startswith(("/Link", "/Possible")):
self._url = ("{0}/otrs/nph-genericinterface.pl/Webservice/"
"{1}{2}".format(self.baseurl,
self.webservicename_link,
route))
else:
self._url = ("{0}/otrs/nph-genericinterface.pl/Webservice/"
"{1}{2}".format(self.baseurl,
self.webservicename,
route))
return self._url
......@@ -1234,18 +1531,18 @@ class Client(object):
**requests.Response**: Response received after sending the request.
.. note::
Supported HTTP Methods: GET, HEAD, PATCH, POST, PUT
Supported HTTP Methods: DELETE, GET, HEAD, PATCH, POST, PUT
"""
if not payload:
raise ArgumentMissingError("payload")
self._result_type = self.type_map[self.operation]
self._result_type = self.operation_map[self.operation]["Result"]
url = self._build_url(ticket_id)
http_method = self.operation_map[self.operation]["RequestMethod"]
if http_method not in ["GET", "HEAD", "PATCH", "POST", "PUT"]:
if http_method not in ["DELETE", "GET", "HEAD", "PATCH", "POST", "PUT"]:
raise ValueError("invalid http_method")
headers = {"Content-Type": "application/json"}
......@@ -1317,6 +1614,19 @@ class Client(object):
self.result = self.result_json['TicketID']
return True
# handle Link operations; Add, Delete, DeleteAll return: {"Success":1}
if self.operation in ["LinkAdd", "LinkDelete", "LinkDeleteAll"]:
if self.result_json.get("Success", None) == 1: # TODO 2016-10-30 (RH): enough?!
return True
# LinkList result can be empty
if self.operation in "LinkList":
_link_list = self.result_json.get("LinkList", None)
if _link_list == u'':
return None
if len(_link_list):
return _link_list
# now handle other operations
if self.result_json.get(self._result_type, None):
self._result_error = False
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment