Skip to content

Commit 3575e9e

Browse files
committed
Merge branch 'feature/fixes_from_changelog_review' into develop
Conflicts: README.md
2 parents 9aebc1d + 948e47a commit 3575e9e

File tree

10 files changed

+175
-130
lines changed

10 files changed

+175
-130
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ proxypid
1212
proxy.log
1313
MANIFEST
1414
coverage_report
15+
test.log

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ examples and unit tests from the SDK.
7171
### Running the examples and unit tests
7272

7373
To run the examples and unit tests, you must put the root of
74-
the SDK on your PYTHONPATH. For example, add the following line to your
74+
the SDK on your PYTHONPATH. For example, if you have downloaded the SDK to your
75+
home folder and are running Mac OS X or Linux, add the following line to your
7576
**.bash_profile**:
7677

7778
export PYTHONPATH=~/splunk-sdk-python

docs/binding.rst

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,5 @@ splunklib.binding
2121
.. autoclass:: HttpLib
2222
:members: delete, get, post, request
2323

24-
.. autoclass:: NoAuthenticationToken
25-
:members:
26-
2724
.. autoclass:: ResponseReader
2825
:members: close, empty, peek, read
29-
30-
.. autoclass:: UrlEncoded
31-
:members:
32-

docs/index.rst

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,13 @@ For more information about the SDK, see the `Splunk Developer Portal <http://dev
1515

1616
:class:`~splunklib.binding.ResponseReader` class
1717

18-
:class:`~splunklib.binding.UrlEncoded` class
19-
2018

2119
**Exceptions**
2220

2321
:func:`~splunklib.binding.handler` function
2422

2523
:class:`~splunklib.binding.AuthenticationError` class
2624

27-
:class:`~splunklib.binding.NoAuthenticationToken` class
28-
2925
**Custom HTTP handler**
3026

3127
:class:`~splunklib.binding.HTTPError` class

splunklib/binding.py

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@
4444
"connect",
4545
"Context",
4646
"handler",
47-
"HTTPError",
48-
"UrlEncoded"
47+
"HTTPError"
4948
]
5049

5150
# If you change these, update the docstring
@@ -75,22 +74,23 @@ class AuthenticationError(Exception):
7574
pass
7675

7776
# Singleton values to eschew None
78-
class NoAuthenticationToken(object):
77+
class _NoAuthenticationToken(object):
7978
"""The value stored in a :class:`Context` or :class:`splunklib.client.Service`
8079
class that is not logged in.
8180
8281
If a ``Context`` or ``Service`` object is created without an authentication
8382
token, and there has not yet been a call to the ``login`` method, the token
8483
field of the ``Context`` or ``Service`` object is set to
85-
``NoAuthenticationToken``.
84+
``_NoAuthenticationToken``.
8685
8786
Likewise, after a ``Context`` or ``Service`` object has been logged out, the
8887
token is set to this value again.
8988
"""
9089
pass
9190

9291
class UrlEncoded(str):
93-
"""This class creates URL-encoded strings.
92+
"""This class marks URL-encoded strings.
93+
It should be considered an SDK-private implementation detail.
9494
9595
Manually tracking whether strings are URL encoded can be difficult. Avoid
9696
calling ``urllib.quote`` to replace special characters with escapes. When
@@ -163,7 +163,7 @@ def __mod__(self, fields):
163163
"""
164164
raise TypeError("Cannot interpolate into a UrlEncoded object.")
165165
def __repr__(self):
166-
return "UrlEncoded('%s')" % urllib.unquote(self)
166+
return "UrlEncoded(%s)" % repr(urllib.unquote(self))
167167

168168
@contextmanager
169169
def _handle_auth_error(msg):
@@ -227,7 +227,7 @@ def f():
227227
"""
228228
@wraps(request_fun)
229229
def wrapper(self, *args, **kwargs):
230-
if self.token is NoAuthenticationToken:
230+
if self.token is _NoAuthenticationToken:
231231
# Not yet logged in.
232232
if self.autologin and self.username and self.password:
233233
# This will throw an uncaught
@@ -403,9 +403,9 @@ class Context(object):
403403
"""
404404
def __init__(self, handler=None, **kwargs):
405405
self.http = HttpLib(handler)
406-
self.token = kwargs.get("token", NoAuthenticationToken)
406+
self.token = kwargs.get("token", _NoAuthenticationToken)
407407
if self.token is None: # In case someone explicitly passes token=None
408-
self.token = NoAuthenticationToken
408+
self.token = _NoAuthenticationToken
409409
self.scheme = kwargs.get("scheme", DEFAULT_SCHEME)
410410
self.host = kwargs.get("host", DEFAULT_HOST)
411411
self.port = int(kwargs.get("port", DEFAULT_PORT))
@@ -427,7 +427,7 @@ def _auth_headers(self):
427427
:returns: A list of 2-tuples containing key and value
428428
"""
429429
# Ensure the token is properly formatted
430-
if self.token.startswith('Splunk'):
430+
if self.token.startswith('Splunk '):
431431
token = self.token
432432
else:
433433
token = 'Splunk %s' % self.token
@@ -447,7 +447,7 @@ def connect(self):
447447
import splunklib.binding as binding
448448
c = binding.connect(...)
449449
socket = c.connect()
450-
socket.write("POST %s HTTP/1.1\\r\\n" % c._abspath("some/path/to/post/to"))
450+
socket.write("POST %s HTTP/1.1\\r\\n" % "some/path/to/post/to")
451451
socket.write("Host: %s:%s\\r\\n" % (c.host, c.port))
452452
socket.write("Accept-Encoding: identity\\r\\n")
453453
socket.write("Authorization: %s\\r\\n" % c.token)
@@ -578,7 +578,7 @@ def get(self, path_segment, owner=None, app=None, sharing=None, **query):
578578

579579
@_authentication
580580
@_log_duration
581-
def post(self, path_segment, owner=None, app=None, sharing=None, headers=[], **query):
581+
def post(self, path_segment, owner=None, app=None, sharing=None, headers=None, **query):
582582
"""Performs a POST operation from the REST path segment with the given
583583
namespace and query.
584584
@@ -610,6 +610,8 @@ def post(self, path_segment, owner=None, app=None, sharing=None, headers=[], **q
610610
:type app: ``string``
611611
:param sharing: The sharing mode of the namespace (optional).
612612
:type sharing: ``string``
613+
:param headers: List of extra HTTP headers to send (optional).
614+
:type headers: ``list`` of 2-tuples.
613615
:param query: All other keyword arguments, which are used as query
614616
parameters.
615617
:type query: ``string``
@@ -638,22 +640,19 @@ def post(self, path_segment, owner=None, app=None, sharing=None, headers=[], **q
638640
c.post('saved/searches', name='boris',
639641
search='search * earliest=-1m | head 1')
640642
"""
643+
if headers is None:
644+
headers = []
645+
641646
path = self.authority + self._abspath(path_segment, owner=owner,
642647
app=app, sharing=sharing)
643648
logging.debug("POST request to %s (body: %s)", path, repr(query))
644-
if isinstance(headers, dict):
645-
all_headers = [(k,v) for k,v in headers.iteritems()]
646-
elif isinstance(headers, list):
647-
all_headers = headers
648-
else:
649-
raise ValueError("headers must be a list or dict (found: %s)" % headers)
650-
all_headers += self._auth_headers
649+
all_headers = headers + self._auth_headers
651650
response = self.http.post(path, all_headers, **query)
652651
return response
653652

654653
@_authentication
655654
@_log_duration
656-
def request(self, path_segment, method="GET", headers=[], body="",
655+
def request(self, path_segment, method="GET", headers=None, body="",
657656
owner=None, app=None, sharing=None):
658657
"""Issues an arbitrary HTTP request to the REST path segment.
659658
@@ -670,6 +669,12 @@ def request(self, path_segment, method="GET", headers=[], body="",
670669
*path_segment*.
671670
:param path_segment: A REST path segment.
672671
:type path_segment: ``string``
672+
:param method: The HTTP method to use (optional).
673+
:type method: ``string``
674+
:param headers: List of extra HTTP headers to send (optional).
675+
:type headers: ``list`` of 2-tuples.
676+
:param body: Content of the HTTP request (optional).
677+
:type body: ``string``
673678
:param owner: The owner context of the namespace (optional).
674679
:type owner: ``string``
675680
:param app: The app context of the namespace (optional).
@@ -701,19 +706,12 @@ def request(self, path_segment, method="GET", headers=[], body="",
701706
c.logout()
702707
c.get('apps/local') # raises AuthenticationError
703708
"""
709+
if headers is None:
710+
headers = []
711+
704712
path = self.authority \
705713
+ self._abspath(path_segment, owner=owner,
706714
app=app, sharing=sharing)
707-
# all_headers can't be named headers, due to how
708-
# Python implements closures. In particular:
709-
# def f(x):
710-
# def g():
711-
# x = x + "a"
712-
# return x
713-
# return g()
714-
# throws UnboundLocalError, since x must be either a member of
715-
# f's local namespace or g's, and cannot switch between them
716-
# during the run of the function.
717715
all_headers = headers + self._auth_headers
718716
logging.debug("%s request to %s (headers: %s, body: %s)",
719717
method, path, str(all_headers), repr(body))
@@ -742,7 +740,7 @@ def login(self):
742740
c = binding.Context(...).login()
743741
# Then issue requests...
744742
"""
745-
if self.token is not NoAuthenticationToken and \
743+
if self.token is not _NoAuthenticationToken and \
746744
(not self.username and not self.password):
747745
# If we were passed a session token, but no username or
748746
# password, then login is a nop, since we're automatically
@@ -765,7 +763,7 @@ def login(self):
765763

766764
def logout(self):
767765
"""Forgets the current session token."""
768-
self.token = NoAuthenticationToken
766+
self.token = _NoAuthenticationToken
769767
return self
770768

771769
def _abspath(self, path_segment,

0 commit comments

Comments
 (0)