@@ -862,6 +862,171 @@ def post(
862862 response = self .http .post (path , all_headers , ** query )
863863 return response
864864
865+
866+ @_authentication
867+ @_log_duration
868+ def put (self , path_segment , object , owner = None , app = None , sharing = None , headers = None , ** query ):
869+ """Performs a PUT operation from the REST path segment with the given object,
870+ namespace and query.
871+
872+ This method is named to match the HTTP method. ``put`` makes at least
873+ one round trip to the server, one additional round trip for each 303
874+ status returned, and at most two additional round trips if
875+ the ``autologin`` field of :func:`connect` is set to ``True``.
876+
877+ If *owner*, *app*, and *sharing* are omitted, this method uses the
878+ default :class:`Context` namespace. All other keyword arguments are
879+ included in the URL as query parameters.
880+
881+ Some of Splunk's endpoints, such as ``receivers/simple`` and
882+ ``receivers/stream``, require unstructured data in the PUT body
883+ and all metadata passed as GET-style arguments. If you provide
884+ a ``body`` argument to ``put``, it will be used as the PUT
885+ body, and all other keyword arguments will be passed as
886+ GET-style arguments in the URL.
887+
888+ :raises AuthenticationError: Raised when the ``Context`` object is not
889+ logged in.
890+ :raises HTTPError: Raised when an error occurred in a GET operation from
891+ *path_segment*.
892+ :param path_segment: A REST path segment.
893+ :type path_segment: ``string``
894+ :param object: The object to be PUT.
895+ :type object: ``string``
896+ :param owner: The owner context of the namespace (optional).
897+ :type owner: ``string``
898+ :param app: The app context of the namespace (optional).
899+ :type app: ``string``
900+ :param sharing: The sharing mode of the namespace (optional).
901+ :type sharing: ``string``
902+ :param headers: List of extra HTTP headers to send (optional).
903+ :type headers: ``list`` of 2-tuples.
904+ :param query: All other keyword arguments, which are used as query
905+ parameters.
906+ :param body: Parameters to be used in the post body. If specified,
907+ any parameters in the query will be applied to the URL instead of
908+ the body. If a dict is supplied, the key-value pairs will be form
909+ encoded. If a string is supplied, the body will be passed through
910+ in the request unchanged.
911+ :type body: ``dict`` or ``str``
912+ :return: The response from the server.
913+ :rtype: ``dict`` with keys ``body``, ``headers``, ``reason``,
914+ and ``status``
915+
916+ **Example**::
917+
918+ c = binding.connect(...)
919+ c.post('saved/searches', name='boris',
920+ search='search * earliest=-1m | head 1') == \\
921+ {'body': ...a response reader object...,
922+ 'headers': [('content-length', '10455'),
923+ ('expires', 'Fri, 30 Oct 1998 00:00:00 GMT'),
924+ ('server', 'Splunkd'),
925+ ('connection', 'close'),
926+ ('cache-control', 'no-store, max-age=0, must-revalidate, no-cache'),
927+ ('date', 'Fri, 11 May 2012 16:46:06 GMT'),
928+ ('content-type', 'text/xml; charset=utf-8')],
929+ 'reason': 'Created',
930+ 'status': 201}
931+ c.post('nonexistant/path') # raises HTTPError
932+ c.logout()
933+ # raises AuthenticationError:
934+ c.put('saved/searches/boris',
935+ search='search * earliest=-1m | head 1')
936+ """
937+ if headers is None :
938+ headers = []
939+
940+ path = self .authority + self ._abspath (path_segment , owner = owner , app = app , sharing = sharing ) + f"/{ object } "
941+
942+ logger .debug ("PUT request to %s (body: %s)" , path , mask_sensitive_data (query ))
943+ all_headers = headers + self .additional_headers + self ._auth_headers
944+ response = self .http .put (path , all_headers , ** query )
945+ return response
946+
947+
948+ @_authentication
949+ @_log_duration
950+ def patch (self , path_segment , object , owner = None , app = None , sharing = None , headers = None , ** query ):
951+ """Performs a PATCH operation from the REST path segment with the given object,
952+ namespace and query.
953+
954+ This method is named to match the HTTP method. ``patch`` makes at least
955+ one round trip to the server, one additional round trip for each 303
956+ status returned, and at most two additional round trips if
957+ the ``autologin`` field of :func:`connect` is set to ``True``.
958+
959+ If *owner*, *app*, and *sharing* are omitted, this method uses the
960+ default :class:`Context` namespace. All other keyword arguments are
961+ included in the URL as query parameters.
962+
963+ Some of Splunk's endpoints, such as ``receivers/simple`` and
964+ ``receivers/stream``, require unstructured data in the PATCH body
965+ and all metadata passed as GET-style arguments. If you provide
966+ a ``body`` argument to ``patch``, it will be used as the PATCH
967+ body, and all other keyword arguments will be passed as
968+ GET-style arguments in the URL.
969+
970+ :raises AuthenticationError: Raised when the ``Context`` object is not
971+ logged in.
972+ :raises HTTPError: Raised when an error occurred in a GET operation from
973+ *path_segment*.
974+ :param path_segment: A REST path segment.
975+ :type path_segment: ``string``
976+ :param object: The object to be PUT.
977+ :type object: ``string``
978+ :param owner: The owner context of the namespace (optional).
979+ :type owner: ``string``
980+ :param app: The app context of the namespace (optional).
981+ :type app: ``string``
982+ :param sharing: The sharing mode of the namespace (optional).
983+ :type sharing: ``string``
984+ :param headers: List of extra HTTP headers to send (optional).
985+ :type headers: ``list`` of 2-tuples.
986+ :param query: All other keyword arguments, which are used as query
987+ parameters.
988+ :param body: Parameters to be used in the post body. If specified,
989+ any parameters in the query will be applied to the URL instead of
990+ the body. If a dict is supplied, the key-value pairs will be form
991+ encoded. If a string is supplied, the body will be passed through
992+ in the request unchanged.
993+ :type body: ``dict`` or ``str``
994+ :return: The response from the server.
995+ :rtype: ``dict`` with keys ``body``, ``headers``, ``reason``,
996+ and ``status``
997+
998+ **Example**::
999+
1000+ c = binding.connect(...)
1001+ c.post('saved/searches', name='boris',
1002+ search='search * earliest=-1m | head 1') == \\
1003+ {'body': ...a response reader object...,
1004+ 'headers': [('content-length', '10455'),
1005+ ('expires', 'Fri, 30 Oct 1998 00:00:00 GMT'),
1006+ ('server', 'Splunkd'),
1007+ ('connection', 'close'),
1008+ ('cache-control', 'no-store, max-age=0, must-revalidate, no-cache'),
1009+ ('date', 'Fri, 11 May 2012 16:46:06 GMT'),
1010+ ('content-type', 'text/xml; charset=utf-8')],
1011+ 'reason': 'Created',
1012+ 'status': 201}
1013+ c.post('nonexistant/path') # raises HTTPError
1014+ c.logout()
1015+ # raises AuthenticationError:
1016+ c.patch('saved/searches/boris',
1017+ search='search * earliest=-1m | head 1')
1018+ """
1019+ if headers is None :
1020+ headers = []
1021+
1022+ path = self .authority + self ._abspath (path_segment , owner = owner , app = app , sharing = sharing ) + f"/{ object } "
1023+
1024+ logger .debug ("PATCH request to %s (body: %s)" , path , mask_sensitive_data (query ))
1025+ all_headers = headers + self .additional_headers + self ._auth_headers
1026+ response = self .http .patch (path , all_headers , ** query )
1027+ return response
1028+
1029+
8651030 @_authentication
8661031 @_log_duration
8671032 def request (
@@ -939,11 +1104,11 @@ def request(
9391104 mask_sensitive_data (body ),
9401105 )
9411106 if body :
942- body = _encode (** body )
943-
9441107 if method == "GET" :
945- path = path + UrlEncoded ("?" + body , skip_encode = True )
946- message = {"method" : method , "headers" : all_headers }
1108+ body = _encode (** body )
1109+ path = path + UrlEncoded ('?' + body , skip_encode = True )
1110+ message = {'method' : method ,
1111+ 'headers' : all_headers }
9471112 else :
9481113 message = {"method" : method , "headers" : all_headers , "body" : body }
9491114 else :
@@ -1301,6 +1466,40 @@ def __init__(
13011466 self .retries = retries
13021467 self .retryDelay = retryDelay
13031468
1469+ def _prepare_request_body_and_url (self , url , headers , ** kwargs ):
1470+ """Helper function to prepare the request body and URL.
1471+
1472+ :param url: The URL.
1473+ :type url: ``string``
1474+ :param headers: A list of pairs specifying the headers for the HTTP request.
1475+ :type headers: ``list``
1476+ :param kwargs: Additional keyword arguments (optional).
1477+ :type kwargs: ``dict``
1478+ :returns: A tuple containing the updated URL, headers, and body.
1479+ :rtype: ``tuple``
1480+ """
1481+ if headers is None :
1482+ headers = []
1483+
1484+ # We handle GET-style arguments and an unstructured body. This is here
1485+ # to support the receivers/stream endpoint.
1486+ if 'body' in kwargs :
1487+ # We only use application/x-www-form-urlencoded if there is no other
1488+ # Content-Type header present. This can happen in cases where we
1489+ # send requests as application/json, e.g. for KV Store.
1490+ if len ([x for x in headers if x [0 ].lower () == "content-type" ]) == 0 :
1491+ headers .append (("Content-Type" , "application/x-www-form-urlencoded" ))
1492+
1493+ body = kwargs .pop ('body' )
1494+ if isinstance (body , dict ):
1495+ body = _encode (** body ).encode ('utf-8' )
1496+ if len (kwargs ) > 0 :
1497+ url = url + UrlEncoded ('?' + _encode (** kwargs ), skip_encode = True )
1498+ else :
1499+ body = _encode (** kwargs ).encode ('utf-8' )
1500+
1501+ return url , headers , body
1502+
13041503 def delete (self , url , headers = None , ** kwargs ):
13051504 """Sends a DELETE request to a URL.
13061505
@@ -1375,28 +1574,66 @@ def post(self, url, headers=None, **kwargs):
13751574 its structure).
13761575 :rtype: ``dict``
13771576 """
1378- if headers is None :
1379- headers = []
1577+ url , headers , body = self ._prepare_request_body_and_url (url , headers , ** kwargs )
1578+ message = {
1579+ 'method' : "POST" ,
1580+ 'headers' : headers ,
1581+ 'body' : body
1582+ }
1583+ return self .request (url , message )
13801584
1381- # We handle GET-style arguments and an unstructured body. This is here
1382- # to support the receivers/stream endpoint.
1383- if "body" in kwargs :
1384- # We only use application/x-www-form-urlencoded if there is no other
1385- # Content-Type header present. This can happen in cases where we
1386- # send requests as application/json, e.g. for KV Store.
1387- if len ([x for x in headers if x [0 ].lower () == "content-type" ]) == 0 :
1388- headers .append (("Content-Type" , "application/x-www-form-urlencoded" ))
1585+ def put (self , url , headers = None , ** kwargs ):
1586+ """Sends a PUT request to a URL.
13891587
1390- body = kwargs .pop ("body" )
1391- if isinstance (body , dict ):
1392- body = _encode (** body ).encode ("utf-8" )
1393- if len (kwargs ) > 0 :
1394- url = url + UrlEncoded ("?" + _encode (** kwargs ), skip_encode = True )
1395- else :
1396- body = _encode (** kwargs ).encode ("utf-8" )
1397- message = {"method" : "POST" , "headers" : headers , "body" : body }
1588+ :param url: The URL.
1589+ :type url: ``string``
1590+ :param headers: A list of pairs specifying the headers for the HTTP
1591+ response (for example, ``[('Content-Type': 'text/cthulhu'), ('Token': 'boris')]``).
1592+ :type headers: ``list``
1593+ :param kwargs: Additional keyword arguments (optional). If the argument
1594+ is ``body``, the value is used as the body for the request, and the
1595+ keywords and their arguments will be URL encoded. If there is no
1596+ ``body`` keyword argument, all the keyword arguments are encoded
1597+ into the body of the request in the format ``x-www-form-urlencoded``.
1598+ :type kwargs: ``dict``
1599+ :returns: A dictionary describing the response (see :class:`HttpLib` for
1600+ its structure).
1601+ :rtype: ``dict``
1602+ """
1603+ url , headers , body = self ._prepare_request_body_and_url (url , headers , ** kwargs )
1604+ message = {
1605+ 'method' : "PUT" ,
1606+ 'headers' : headers ,
1607+ 'body' : body
1608+ }
13981609 return self .request (url , message )
1610+
1611+ def patch (self , url , headers = None , ** kwargs ):
1612+ """Sends a PATCH request to a URL.
13991613
1614+ :param url: The URL.
1615+ :type url: ``string``
1616+ :param headers: A list of pairs specifying the headers for the HTTP
1617+ response (for example, ``[('Content-Type': 'text/cthulhu'), ('Token': 'boris')]``).
1618+ :type headers: ``list``
1619+ :param kwargs: Additional keyword arguments (optional). If the argument
1620+ is ``body``, the value is used as the body for the request, and the
1621+ keywords and their arguments will be URL encoded. If there is no
1622+ ``body`` keyword argument, all the keyword arguments are encoded
1623+ into the body of the request in the format ``x-www-form-urlencoded``.
1624+ :type kwargs: ``dict``
1625+ :returns: A dictionary describing the response (see :class:`HttpLib` for
1626+ its structure).
1627+ :rtype: ``dict``
1628+ """
1629+ url , headers , body = self ._prepare_request_body_and_url (url , headers , ** kwargs )
1630+ message = {
1631+ 'method' : "PATCH" ,
1632+ 'headers' : headers ,
1633+ 'body' : body
1634+ }
1635+ return self .request (url , message )
1636+
14001637 def request (self , url , message , ** kwargs ):
14011638 """Issues an HTTP request to a URL.
14021639
0 commit comments