@@ -809,6 +809,171 @@ def post(self, path_segment, owner=None, app=None, sharing=None, headers=None, *
809809 response = self .http .post (path , all_headers , ** query )
810810 return response
811811
812+
813+ @_authentication
814+ @_log_duration
815+ def put (self , path_segment , object , owner = None , app = None , sharing = None , headers = None , ** query ):
816+ """Performs a PUT operation from the REST path segment with the given object,
817+ namespace and query.
818+
819+ This method is named to match the HTTP method. ``put`` makes at least
820+ one round trip to the server, one additional round trip for each 303
821+ status returned, and at most two additional round trips if
822+ the ``autologin`` field of :func:`connect` is set to ``True``.
823+
824+ If *owner*, *app*, and *sharing* are omitted, this method uses the
825+ default :class:`Context` namespace. All other keyword arguments are
826+ included in the URL as query parameters.
827+
828+ Some of Splunk's endpoints, such as ``receivers/simple`` and
829+ ``receivers/stream``, require unstructured data in the PUT body
830+ and all metadata passed as GET-style arguments. If you provide
831+ a ``body`` argument to ``put``, it will be used as the PUT
832+ body, and all other keyword arguments will be passed as
833+ GET-style arguments in the URL.
834+
835+ :raises AuthenticationError: Raised when the ``Context`` object is not
836+ logged in.
837+ :raises HTTPError: Raised when an error occurred in a GET operation from
838+ *path_segment*.
839+ :param path_segment: A REST path segment.
840+ :type path_segment: ``string``
841+ :param object: The object to be PUT.
842+ :type object: ``string``
843+ :param owner: The owner context of the namespace (optional).
844+ :type owner: ``string``
845+ :param app: The app context of the namespace (optional).
846+ :type app: ``string``
847+ :param sharing: The sharing mode of the namespace (optional).
848+ :type sharing: ``string``
849+ :param headers: List of extra HTTP headers to send (optional).
850+ :type headers: ``list`` of 2-tuples.
851+ :param query: All other keyword arguments, which are used as query
852+ parameters.
853+ :param body: Parameters to be used in the post body. If specified,
854+ any parameters in the query will be applied to the URL instead of
855+ the body. If a dict is supplied, the key-value pairs will be form
856+ encoded. If a string is supplied, the body will be passed through
857+ in the request unchanged.
858+ :type body: ``dict`` or ``str``
859+ :return: The response from the server.
860+ :rtype: ``dict`` with keys ``body``, ``headers``, ``reason``,
861+ and ``status``
862+
863+ **Example**::
864+
865+ c = binding.connect(...)
866+ c.post('saved/searches', name='boris',
867+ search='search * earliest=-1m | head 1') == \\
868+ {'body': ...a response reader object...,
869+ 'headers': [('content-length', '10455'),
870+ ('expires', 'Fri, 30 Oct 1998 00:00:00 GMT'),
871+ ('server', 'Splunkd'),
872+ ('connection', 'close'),
873+ ('cache-control', 'no-store, max-age=0, must-revalidate, no-cache'),
874+ ('date', 'Fri, 11 May 2012 16:46:06 GMT'),
875+ ('content-type', 'text/xml; charset=utf-8')],
876+ 'reason': 'Created',
877+ 'status': 201}
878+ c.post('nonexistant/path') # raises HTTPError
879+ c.logout()
880+ # raises AuthenticationError:
881+ c.put('saved/searches/boris',
882+ search='search * earliest=-1m | head 1')
883+ """
884+ if headers is None :
885+ headers = []
886+
887+ path = self .authority + self ._abspath (path_segment , owner = owner , app = app , sharing = sharing ) + f"/{ object } "
888+
889+ logger .debug ("PUT request to %s (body: %s)" , path , mask_sensitive_data (query ))
890+ all_headers = headers + self .additional_headers + self ._auth_headers
891+ response = self .http .put (path , all_headers , ** query )
892+ return response
893+
894+
895+ @_authentication
896+ @_log_duration
897+ def patch (self , path_segment , object , owner = None , app = None , sharing = None , headers = None , ** query ):
898+ """Performs a PATCH operation from the REST path segment with the given object,
899+ namespace and query.
900+
901+ This method is named to match the HTTP method. ``patch`` makes at least
902+ one round trip to the server, one additional round trip for each 303
903+ status returned, and at most two additional round trips if
904+ the ``autologin`` field of :func:`connect` is set to ``True``.
905+
906+ If *owner*, *app*, and *sharing* are omitted, this method uses the
907+ default :class:`Context` namespace. All other keyword arguments are
908+ included in the URL as query parameters.
909+
910+ Some of Splunk's endpoints, such as ``receivers/simple`` and
911+ ``receivers/stream``, require unstructured data in the PATCH body
912+ and all metadata passed as GET-style arguments. If you provide
913+ a ``body`` argument to ``patch``, it will be used as the PATCH
914+ body, and all other keyword arguments will be passed as
915+ GET-style arguments in the URL.
916+
917+ :raises AuthenticationError: Raised when the ``Context`` object is not
918+ logged in.
919+ :raises HTTPError: Raised when an error occurred in a GET operation from
920+ *path_segment*.
921+ :param path_segment: A REST path segment.
922+ :type path_segment: ``string``
923+ :param object: The object to be PUT.
924+ :type object: ``string``
925+ :param owner: The owner context of the namespace (optional).
926+ :type owner: ``string``
927+ :param app: The app context of the namespace (optional).
928+ :type app: ``string``
929+ :param sharing: The sharing mode of the namespace (optional).
930+ :type sharing: ``string``
931+ :param headers: List of extra HTTP headers to send (optional).
932+ :type headers: ``list`` of 2-tuples.
933+ :param query: All other keyword arguments, which are used as query
934+ parameters.
935+ :param body: Parameters to be used in the post body. If specified,
936+ any parameters in the query will be applied to the URL instead of
937+ the body. If a dict is supplied, the key-value pairs will be form
938+ encoded. If a string is supplied, the body will be passed through
939+ in the request unchanged.
940+ :type body: ``dict`` or ``str``
941+ :return: The response from the server.
942+ :rtype: ``dict`` with keys ``body``, ``headers``, ``reason``,
943+ and ``status``
944+
945+ **Example**::
946+
947+ c = binding.connect(...)
948+ c.post('saved/searches', name='boris',
949+ search='search * earliest=-1m | head 1') == \\
950+ {'body': ...a response reader object...,
951+ 'headers': [('content-length', '10455'),
952+ ('expires', 'Fri, 30 Oct 1998 00:00:00 GMT'),
953+ ('server', 'Splunkd'),
954+ ('connection', 'close'),
955+ ('cache-control', 'no-store, max-age=0, must-revalidate, no-cache'),
956+ ('date', 'Fri, 11 May 2012 16:46:06 GMT'),
957+ ('content-type', 'text/xml; charset=utf-8')],
958+ 'reason': 'Created',
959+ 'status': 201}
960+ c.post('nonexistant/path') # raises HTTPError
961+ c.logout()
962+ # raises AuthenticationError:
963+ c.patch('saved/searches/boris',
964+ search='search * earliest=-1m | head 1')
965+ """
966+ if headers is None :
967+ headers = []
968+
969+ path = self .authority + self ._abspath (path_segment , owner = owner , app = app , sharing = sharing ) + f"/{ object } "
970+
971+ logger .debug ("PATCH request to %s (body: %s)" , path , mask_sensitive_data (query ))
972+ all_headers = headers + self .additional_headers + self ._auth_headers
973+ response = self .http .patch (path , all_headers , ** query )
974+ return response
975+
976+
812977 @_authentication
813978 @_log_duration
814979 def request (self , path_segment , method = "GET" , headers = None , body = {},
@@ -1210,6 +1375,40 @@ def __init__(self, custom_handler=None, verify=False, key_file=None, cert_file=N
12101375 self .retries = retries
12111376 self .retryDelay = retryDelay
12121377
1378+ def _prepare_request_body_and_url (self , url , headers , ** kwargs ):
1379+ """Helper function to prepare the request body and URL.
1380+
1381+ :param url: The URL.
1382+ :type url: ``string``
1383+ :param headers: A list of pairs specifying the headers for the HTTP request.
1384+ :type headers: ``list``
1385+ :param kwargs: Additional keyword arguments (optional).
1386+ :type kwargs: ``dict``
1387+ :returns: A tuple containing the updated URL, headers, and body.
1388+ :rtype: ``tuple``
1389+ """
1390+ if headers is None :
1391+ headers = []
1392+
1393+ # We handle GET-style arguments and an unstructured body. This is here
1394+ # to support the receivers/stream endpoint.
1395+ if 'body' in kwargs :
1396+ # We only use application/x-www-form-urlencoded if there is no other
1397+ # Content-Type header present. This can happen in cases where we
1398+ # send requests as application/json, e.g. for KV Store.
1399+ if len ([x for x in headers if x [0 ].lower () == "content-type" ]) == 0 :
1400+ headers .append (("Content-Type" , "application/x-www-form-urlencoded" ))
1401+
1402+ body = kwargs .pop ('body' )
1403+ if isinstance (body , dict ):
1404+ body = _encode (** body ).encode ('utf-8' )
1405+ if len (kwargs ) > 0 :
1406+ url = url + UrlEncoded ('?' + _encode (** kwargs ), skip_encode = True )
1407+ else :
1408+ body = _encode (** kwargs ).encode ('utf-8' )
1409+
1410+ return url , headers , body
1411+
12131412 def delete (self , url , headers = None , ** kwargs ):
12141413 """Sends a DELETE request to a URL.
12151414
@@ -1282,31 +1481,66 @@ def post(self, url, headers=None, **kwargs):
12821481 its structure).
12831482 :rtype: ``dict``
12841483 """
1285- if headers is None : headers = []
1484+ url , headers , body = self ._prepare_request_body_and_url (url , headers , ** kwargs )
1485+ message = {
1486+ 'method' : "POST" ,
1487+ 'headers' : headers ,
1488+ 'body' : body
1489+ }
1490+ return self .request (url , message )
12861491
1287- # We handle GET-style arguments and an unstructured body. This is here
1288- # to support the receivers/stream endpoint.
1289- if 'body' in kwargs :
1290- # We only use application/x-www-form-urlencoded if there is no other
1291- # Content-Type header present. This can happen in cases where we
1292- # send requests as application/json, e.g. for KV Store.
1293- if len ([x for x in headers if x [0 ].lower () == "content-type" ]) == 0 :
1294- headers .append (("Content-Type" , "application/x-www-form-urlencoded" ))
1492+ def put (self , url , headers = None , ** kwargs ):
1493+ """Sends a PUT request to a URL.
12951494
1296- body = kwargs .pop ('body' )
1297- if isinstance (body , dict ):
1298- body = _encode (** body ).encode ('utf-8' )
1299- if len (kwargs ) > 0 :
1300- url = url + UrlEncoded ('?' + _encode (** kwargs ), skip_encode = True )
1301- else :
1302- body = _encode (** kwargs ).encode ('utf-8' )
1495+ :param url: The URL.
1496+ :type url: ``string``
1497+ :param headers: A list of pairs specifying the headers for the HTTP
1498+ response (for example, ``[('Content-Type': 'text/cthulhu'), ('Token': 'boris')]``).
1499+ :type headers: ``list``
1500+ :param kwargs: Additional keyword arguments (optional). If the argument
1501+ is ``body``, the value is used as the body for the request, and the
1502+ keywords and their arguments will be URL encoded. If there is no
1503+ ``body`` keyword argument, all the keyword arguments are encoded
1504+ into the body of the request in the format ``x-www-form-urlencoded``.
1505+ :type kwargs: ``dict``
1506+ :returns: A dictionary describing the response (see :class:`HttpLib` for
1507+ its structure).
1508+ :rtype: ``dict``
1509+ """
1510+ url , headers , body = self ._prepare_request_body_and_url (url , headers , ** kwargs )
13031511 message = {
1304- 'method' : "POST " ,
1512+ 'method' : "PUT " ,
13051513 'headers' : headers ,
13061514 'body' : body
13071515 }
13081516 return self .request (url , message )
1517+
1518+ def patch (self , url , headers = None , ** kwargs ):
1519+ """Sends a PATCH request to a URL.
13091520
1521+ :param url: The URL.
1522+ :type url: ``string``
1523+ :param headers: A list of pairs specifying the headers for the HTTP
1524+ response (for example, ``[('Content-Type': 'text/cthulhu'), ('Token': 'boris')]``).
1525+ :type headers: ``list``
1526+ :param kwargs: Additional keyword arguments (optional). If the argument
1527+ is ``body``, the value is used as the body for the request, and the
1528+ keywords and their arguments will be URL encoded. If there is no
1529+ ``body`` keyword argument, all the keyword arguments are encoded
1530+ into the body of the request in the format ``x-www-form-urlencoded``.
1531+ :type kwargs: ``dict``
1532+ :returns: A dictionary describing the response (see :class:`HttpLib` for
1533+ its structure).
1534+ :rtype: ``dict``
1535+ """
1536+ url , headers , body = self ._prepare_request_body_and_url (url , headers , ** kwargs )
1537+ message = {
1538+ 'method' : "PATCH" ,
1539+ 'headers' : headers ,
1540+ 'body' : body
1541+ }
1542+ return self .request (url , message )
1543+
13101544 def request (self , url , message , ** kwargs ):
13111545 """Issues an HTTP request to a URL.
13121546
0 commit comments