@@ -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 = {},
@@ -873,9 +1038,8 @@ def request(self, path_segment, method="GET", headers=None, body={},
8731038 logger .debug ("%s request to %s (headers: %s, body: %s)" ,
8741039 method , path , str (mask_sensitive_data (dict (all_headers ))), mask_sensitive_data (body ))
8751040 if body :
876- body = _encode (** body )
877-
8781041 if method == "GET" :
1042+ body = _encode (** body )
8791043 path = path + UrlEncoded ('?' + body , skip_encode = True )
8801044 message = {'method' : method ,
8811045 'headers' : all_headers }
@@ -1210,6 +1374,40 @@ def __init__(self, custom_handler=None, verify=False, key_file=None, cert_file=N
12101374 self .retries = retries
12111375 self .retryDelay = retryDelay
12121376
1377+ def _prepare_request_body_and_url (self , url , headers , ** kwargs ):
1378+ """Helper function to prepare the request body and URL.
1379+
1380+ :param url: The URL.
1381+ :type url: ``string``
1382+ :param headers: A list of pairs specifying the headers for the HTTP request.
1383+ :type headers: ``list``
1384+ :param kwargs: Additional keyword arguments (optional).
1385+ :type kwargs: ``dict``
1386+ :returns: A tuple containing the updated URL, headers, and body.
1387+ :rtype: ``tuple``
1388+ """
1389+ if headers is None :
1390+ headers = []
1391+
1392+ # We handle GET-style arguments and an unstructured body. This is here
1393+ # to support the receivers/stream endpoint.
1394+ if 'body' in kwargs :
1395+ # We only use application/x-www-form-urlencoded if there is no other
1396+ # Content-Type header present. This can happen in cases where we
1397+ # send requests as application/json, e.g. for KV Store.
1398+ if len ([x for x in headers if x [0 ].lower () == "content-type" ]) == 0 :
1399+ headers .append (("Content-Type" , "application/x-www-form-urlencoded" ))
1400+
1401+ body = kwargs .pop ('body' )
1402+ if isinstance (body , dict ):
1403+ body = _encode (** body ).encode ('utf-8' )
1404+ if len (kwargs ) > 0 :
1405+ url = url + UrlEncoded ('?' + _encode (** kwargs ), skip_encode = True )
1406+ else :
1407+ body = _encode (** kwargs ).encode ('utf-8' )
1408+
1409+ return url , headers , body
1410+
12131411 def delete (self , url , headers = None , ** kwargs ):
12141412 """Sends a DELETE request to a URL.
12151413
@@ -1282,31 +1480,66 @@ def post(self, url, headers=None, **kwargs):
12821480 its structure).
12831481 :rtype: ``dict``
12841482 """
1285- if headers is None : headers = []
1483+ url , headers , body = self ._prepare_request_body_and_url (url , headers , ** kwargs )
1484+ message = {
1485+ 'method' : "POST" ,
1486+ 'headers' : headers ,
1487+ 'body' : body
1488+ }
1489+ return self .request (url , message )
12861490
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" ))
1491+ def put (self , url , headers = None , ** kwargs ):
1492+ """Sends a PUT request to a URL.
12951493
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' )
1494+ :param url: The URL.
1495+ :type url: ``string``
1496+ :param headers: A list of pairs specifying the headers for the HTTP
1497+ response (for example, ``[('Content-Type': 'text/cthulhu'), ('Token': 'boris')]``).
1498+ :type headers: ``list``
1499+ :param kwargs: Additional keyword arguments (optional). If the argument
1500+ is ``body``, the value is used as the body for the request, and the
1501+ keywords and their arguments will be URL encoded. If there is no
1502+ ``body`` keyword argument, all the keyword arguments are encoded
1503+ into the body of the request in the format ``x-www-form-urlencoded``.
1504+ :type kwargs: ``dict``
1505+ :returns: A dictionary describing the response (see :class:`HttpLib` for
1506+ its structure).
1507+ :rtype: ``dict``
1508+ """
1509+ url , headers , body = self ._prepare_request_body_and_url (url , headers , ** kwargs )
13031510 message = {
1304- 'method' : "POST " ,
1511+ 'method' : "PUT " ,
13051512 'headers' : headers ,
13061513 'body' : body
13071514 }
13081515 return self .request (url , message )
1516+
1517+ def patch (self , url , headers = None , ** kwargs ):
1518+ """Sends a PATCH request to a URL.
13091519
1520+ :param url: The URL.
1521+ :type url: ``string``
1522+ :param headers: A list of pairs specifying the headers for the HTTP
1523+ response (for example, ``[('Content-Type': 'text/cthulhu'), ('Token': 'boris')]``).
1524+ :type headers: ``list``
1525+ :param kwargs: Additional keyword arguments (optional). If the argument
1526+ is ``body``, the value is used as the body for the request, and the
1527+ keywords and their arguments will be URL encoded. If there is no
1528+ ``body`` keyword argument, all the keyword arguments are encoded
1529+ into the body of the request in the format ``x-www-form-urlencoded``.
1530+ :type kwargs: ``dict``
1531+ :returns: A dictionary describing the response (see :class:`HttpLib` for
1532+ its structure).
1533+ :rtype: ``dict``
1534+ """
1535+ url , headers , body = self ._prepare_request_body_and_url (url , headers , ** kwargs )
1536+ message = {
1537+ 'method' : "PATCH" ,
1538+ 'headers' : headers ,
1539+ 'body' : body
1540+ }
1541+ return self .request (url , message )
1542+
13101543 def request (self , url , message , ** kwargs ):
13111544 """Issues an HTTP request to a URL.
13121545
0 commit comments