@@ -1154,10 +1154,11 @@ def __getitem__(self, key):
11541154 # x[a,b] is translated to x.__getitem__( (a,b) ), so we
11551155 # have to extract values out.
11561156 key , ns = key
1157+ key = UrlEncoded (key , encode_slash = True )
11571158 response = self .get (key , owner = ns .owner , app = ns .app )
11581159 else :
1160+ key = UrlEncoded (key , encode_slash = True )
11591161 response = self .get (key )
1160-
11611162 entries = self ._load_list (response )
11621163 if len (entries ) > 1 :
11631164 raise AmbiguousReferenceException ("Found multiple entities named '%s'; please specify a namespace." % key )
@@ -1191,6 +1192,7 @@ def __iter__(self, **kwargs):
11911192 for entity in saved_searches:
11921193 print "Saved search named %s" % entity.name
11931194 """
1195+
11941196 for item in self .iter (** kwargs ):
11951197 yield item
11961198
@@ -1397,6 +1399,8 @@ def list(self, count=None, **kwargs):
13971399 return list (self .iter (count = count , ** kwargs ))
13981400
13991401
1402+
1403+
14001404class Collection (ReadOnlyCollection ):
14011405 """A collection of entities.
14021406
@@ -1442,6 +1446,7 @@ class Collection(ReadOnlyCollection):
14421446 :class:`Collection` does no caching. Each call makes at least one
14431447 round trip to the server to fetch data.
14441448 """
1449+
14451450 def create (self , name , ** params ):
14461451 """Creates a new entity in this collection.
14471452
@@ -1516,6 +1521,7 @@ def delete(self, name, **params):
15161521 saved_searches.delete('my_saved_search')
15171522 assert 'my_saved_search' not in saved_searches
15181523 """
1524+ name = UrlEncoded (name , encode_slash = True )
15191525 if 'namespace' in params :
15201526 namespace = params .pop ('namespace' )
15211527 params ['owner' ] = namespace .owner
@@ -1533,6 +1539,56 @@ def delete(self, name, **params):
15331539 raise
15341540 return self
15351541
1542+ def get (self , name = "" , owner = None , app = None , sharing = None , ** query ):
1543+ """Performs a GET request to the server on the collection.
1544+
1545+ If *owner*, *app*, and *sharing* are omitted, this method takes a
1546+ default namespace from the :class:`Service` object for this :class:`Endpoint`.
1547+ All other keyword arguments are included in the URL as query parameters.
1548+
1549+ :raises AuthenticationError: Raised when the ``Service`` is not logged in.
1550+ :raises HTTPError: Raised when an error in the request occurs.
1551+ :param path_segment: A path segment relative to this endpoint.
1552+ :type path_segment: ``string``
1553+ :param owner: The owner context of the namespace (optional).
1554+ :type owner: ``string``
1555+ :param app: The app context of the namespace (optional).
1556+ :type app: ``string``
1557+ :param sharing: The sharing mode for the namespace (optional).
1558+ :type sharing: "global", "system", "app", or "user"
1559+ :param query: All other keyword arguments, which are used as query
1560+ parameters.
1561+ :type query: ``string``
1562+ :return: The response from the server.
1563+ :rtype: ``dict`` with keys ``body``, ``headers``, ``reason``,
1564+ and ``status``
1565+
1566+ Example:
1567+
1568+ import splunklib.client
1569+ s = client.service(...)
1570+ saved_searches = s.saved_searches
1571+ saved_searches.get("my/saved/search") == \\
1572+ {'body': ...a response reader object...,
1573+ 'headers': [('content-length', '26208'),
1574+ ('expires', 'Fri, 30 Oct 1998 00:00:00 GMT'),
1575+ ('server', 'Splunkd'),
1576+ ('connection', 'close'),
1577+ ('cache-control', 'no-store, max-age=0, must-revalidate, no-cache'),
1578+ ('date', 'Fri, 11 May 2012 16:30:35 GMT'),
1579+ ('content-type', 'text/xml; charset=utf-8')],
1580+ 'reason': 'OK',
1581+ 'status': 200}
1582+ saved_searches.get('nonexistant/search') # raises HTTPError
1583+ s.logout()
1584+ saved_searches.get() # raises AuthenticationError
1585+
1586+ """
1587+ name = UrlEncoded (name , encode_slash = True )
1588+ return super (Collection , self ).get (name , owner , app , sharing , ** query )
1589+
1590+
1591+
15361592
15371593class ConfigurationFile (Collection ):
15381594 """This class contains all of the stanzas from one configuration file.
@@ -1953,9 +2009,13 @@ def __init__(self, service, kindmap=None):
19532009 Collection .__init__ (self , service , PATH_INPUTS , item = Input )
19542010
19552011 def __getitem__ (self , key ):
2012+ # The key needed to retrieve the input needs it's parenthesis to be URL encoded
2013+ # based on the REST API for input
2014+ # <http://docs.splunk.com/Documentation/Splunk/latest/RESTAPI/RESTinput>
19562015 if isinstance (key , tuple ) and len (key ) == 2 :
19572016 # Fetch a single kind
19582017 key , kind = key
2018+ key = UrlEncoded (key , encode_slash = True )
19592019 try :
19602020 response = self .get (self .kindpath (kind ) + "/" + key )
19612021 entries = self ._load_list (response )
@@ -1974,6 +2034,7 @@ def __getitem__(self, key):
19742034 # Iterate over all the kinds looking for matches.
19752035 kind = None
19762036 candidate = None
2037+ key = UrlEncoded (key , encode_slash = True )
19772038 for kind in self .kinds :
19782039 try :
19792040 response = self .get (kind + "/" + key )
@@ -1992,7 +2053,7 @@ def __getitem__(self, key):
19922053 else :
19932054 raise
19942055 if candidate is None :
1995- raise KeyError (key ) # Never found a match
2056+ raise KeyError (key ) # Never found a match.
19962057 else :
19972058 return candidate
19982059
@@ -2065,11 +2126,14 @@ def create(self, name, kind, **kwargs):
20652126 # If we created an input with restrictToHost set, then
20662127 # its path will be <restrictToHost>:<name>, not just <name>,
20672128 # and we have to adjust accordingly.
2129+
2130+ # Url encodes the name of the entity.
2131+ name = UrlEncoded (name , encode_slash = True )
20682132 path = _path (
20692133 self .path + kindpath ,
20702134 '%s:%s' % (kwargs ['restrictToHost' ], name ) \
20712135 if kwargs .has_key ('restrictToHost' ) else name
2072- )
2136+ )
20732137 return Input (self .service , path , kind )
20742138
20752139 def delete (self , name , kind = None ):
@@ -2146,7 +2210,7 @@ def itemmeta(self, kind):
21462210 def _get_kind_list (self , subpath = None ):
21472211 if subpath is None :
21482212 subpath = []
2149-
2213+
21502214 kinds = []
21512215 response = self .get ('/' .join (subpath ))
21522216 content = _load_atom_entries (response )
@@ -2204,12 +2268,12 @@ def kindpath(self, kind):
22042268 :rtype: ``string``
22052269 """
22062270 if kind in self .kinds :
2207- return kind
2271+ return UrlEncoded ( kind , skip_encode = True )
22082272 # Special cases
22092273 elif kind == 'tcp' :
2210- return 'tcp/raw'
2274+ return UrlEncoded ( 'tcp/raw' , skip_encode = True )
22112275 elif kind == 'splunktcp' :
2212- return 'tcp/cooked'
2276+ return UrlEncoded ( 'tcp/cooked' , skip_encode = True )
22132277 else :
22142278 raise ValueError ("No such kind on server: %s" % kind )
22152279
@@ -2276,6 +2340,7 @@ def list(self, *kinds, **kwargs):
22762340 path = self .kindpath (kind )
22772341 logging .debug ("Path for inputs: %s" , path )
22782342 try :
2343+ path = UrlEncoded (path , skip_encode = True )
22792344 response = self .get (path , ** kwargs )
22802345 except HTTPError , he :
22812346 if he .status == 404 : # No inputs of this kind
@@ -2300,6 +2365,7 @@ def list(self, *kinds, **kwargs):
23002365 for kind in kinds :
23012366 response = None
23022367 try :
2368+ kind = UrlEncoded (kind , skip_encode = True )
23032369 response = self .get (self .kindpath (kind ), search = search )
23042370 except HTTPError as e :
23052371 if e .status == 404 :
0 commit comments