Skip to content

Commit 805b4d0

Browse files
committed
Merge branch 'develop' of github.com:splunk/splunk-sdk-python into develop
2 parents 7401199 + d835ac0 commit 805b4d0

14 files changed

+80
-23
lines changed

CHANGELOG.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,27 @@
1515

1616
2. Logs a traceback to SearchCommand.logger. This is old behavior.
1717

18-
### Bug fixes
18+
* Made ResponseReader more streamlike, so that it can be wrapped in an
19+
io.BufferedReader to realize a significant performance gain.
20+
21+
*Example usage*
1922

20-
1. Addressed a problem in the results reader running under Python 2.6.
23+
```
24+
import io
25+
...
26+
response = job.results(count=maxRecords, offset=self._offset)
27+
resultsList = results.ResultsReader(io.BufferedReader(response))
28+
```
2129

22-
The results reader now catches SyntaxError exceptions instead of
30+
### Bug fixes
31+
32+
1. The results reader now catches SyntaxError exceptions instead of
2333
`xml.etree.ElementTree.ParseError` exceptions. `ParseError` wasn't
24-
introduced until Python 2.7. This masked the root cause of certain issues
25-
with result elements.
34+
introduced until Python 2.7. This masked the root cause of errors
35+
data errors in result elements.
2636

37+
2. When writing a ReportingCommand you no longer need to include a map method.
38+
2739
## Version 1.2.2
2840

2941
### Bug fixes

Commands.conf.spec.xlsx

15 KB
Binary file not shown.

splunklib/client.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2718,10 +2718,11 @@ def export(self, query, **params):
27182718
# Normal events are returned as dicts
27192719
print result
27202720
assert rr.is_preview == False
2721-
2722-
Running an export search is more efficient than running a similar
2723-
preview search because no post-processing is done on the retrieved
2724-
events. The raw events are simply returned.
2721+
2722+
Running an export search is more efficient as it streams the results
2723+
directly to you, rather than having to write them out to disk and make
2724+
them available later. As soon as results are ready, you will receive
2725+
them.
27252726
27262727
The ``export`` method makes a single roundtrip to the server (as opposed
27272728
to two for :meth:`create` followed by :meth:`preview`), plus at most two

splunklib/searchcommands/csv/dict_writer.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
# under the License.
1414

1515
from __future__ import absolute_import
16+
17+
from numbers import Number
1618
import csv
1719

1820

@@ -59,13 +61,24 @@ def _encode_list(self, value):
5961
if len(value) == 1:
6062
return value[0], None
6163
multi_value = ';'.join(
62-
['$' + repr(item).replace('$', '$$') + '$' for item in value])
64+
['$' + DictWriter._to_string(item).replace('$', '$$') + '$' for item
65+
in value])
6366
value = self._mv_delimiter.join([repr(item) for item in value])
6467
return value, multi_value
6568

6669
def _header_written(self):
6770
return self._fieldnames is not None
6871

72+
@staticmethod
73+
def _to_string(item):
74+
if isinstance(item, bool):
75+
return 't' if item else 'f'
76+
if isinstance(item, str):
77+
return item
78+
if isinstance(item, Number):
79+
return str(item)
80+
return repr(item)
81+
6982
def _writeheader(self, record):
7083
if self.fieldnames is None:
7184
self.fieldnames = sorted(record.keys())

splunklib/searchcommands/reporting_command.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,16 @@ def streaming_preop(self):
162162
Computed.
163163
164164
"""
165+
command = type(self.command)
166+
167+
if command.map == ReportingCommand.map:
168+
return ""
169+
165170
command_line = str(self.command)
166171
command_name = type(self.command).name
167172
text = ' '.join([
168173
command_name, '__map__', command_line[len(command_name) + 1:]])
174+
169175
return text
170176

171177
#endregion
@@ -196,15 +202,15 @@ def fix_up(cls, command):
196202
if command.reduce == ReportingCommand.reduce:
197203
raise AttributeError('No ReportingCommand.reduce override')
198204

205+
if command.map == ReportingCommand.map:
206+
cls._requires_preop = False
207+
return
208+
199209
f = vars(command)['map'] # Function backing the map method
200210
# There is no way to add custom attributes to methods. See
201211
# [Why does setattr fail on a method](http://goo.gl/aiOsqh)
202212
# for an explanation.
203213

204-
if f == vars(ReportingCommand)['map']:
205-
cls._requires_preop = False
206-
return
207-
208214
try:
209215
settings = f._settings
210216
except AttributeError:

tests/searchcommands/test_decorators.py

100644100755
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,7 @@ def test_builtin_options(self):
183183

184184
return
185185

186-
_package_directory = os.path.dirname(__file__)
186+
_package_directory = os.path.dirname(__file__)
187+
188+
if __name__ == "__main__":
189+
unittest.main()

tests/searchcommands/test_examples.py

100644100755
File mode changed.

tests/searchcommands/test_search_command.py

100644100755
Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,18 @@ def test_process(self):
5454
# We support dynamic configuration, not static
5555

5656
expected = \
57-
'error_message=Command search appears to be statically configured and static configuration is unsupported by splunklib.searchcommands. Please ensure that default/commands.conf contains this stanza: [search] | filename = foo.py | supports_getinfo = true | supports_rawargs = true | outputheader = true\r\n' \
57+
'\r\n' \
58+
'ERROR,__mv_ERROR' \
59+
'\r\n' \
60+
'Command search appears to be statically configured and static configuration is unsupported by splunklib.searchcommands. Please ensure that default/commands.conf contains this stanza: [search] | filename = foo.py | supports_getinfo = true | supports_rawargs = true | outputheader = true,' \
5861
'\r\n'
5962

6063
command = SearchCommand()
6164
result = StringIO()
62-
command.process(['foo.py'], output_file=result)
65+
66+
self.assertRaises(
67+
SystemExit, command.process, ['foo.py'], output_file=result)
68+
6369
result.reset()
6470
observed = result.read()
6571
self.assertEqual(expected, observed)
@@ -88,7 +94,8 @@ def test_process(self):
8894

8995
command = SearchCommand()
9096
result = StringIO()
91-
command.process(['foo.py', '__GETINFO__', 'undefined_option=value'], output_file=result)
97+
98+
self.assertRaises(SystemExit, command.process, ['foo.py', '__GETINFO__', 'undefined_option=value'], output_file=result)
9299
result.reset()
93100
observed = result.read()
94101
self.assertEqual(expected, observed)
@@ -97,8 +104,9 @@ def test_process(self):
97104
# errors, if invoked to execute
98105

99106
expected = \
100-
'error_message=Unrecognized option: undefined_option = value\r\n' \
101-
'\r\n'
107+
'\r\n' \
108+
'ERROR,__mv_ERROR\r\n' \
109+
'Unrecognized option: undefined_option = value,\r\n'
102110

103111
command = SearchCommand()
104112
result = StringIO()
@@ -118,7 +126,9 @@ def test_process(self):
118126
# Command.process should exit on processing exceptions
119127

120128
expected = \
121-
''
129+
'\r\n' \
130+
'ERROR,__mv_ERROR\r\n' \
131+
'\'NoneType\' object is not iterable,\r\n'
122132

123133
command = SearchCommand()
124134
result = StringIO()
@@ -175,4 +185,7 @@ def test_process(self):
175185

176186
return
177187

178-
_package_directory = os.path.dirname(__file__)
188+
_package_directory = os.path.dirname(__file__)
189+
190+
if __name__ == "__main__":
191+
unittest.main()

tests/searchcommands/test_search_command_internals.py

100644100755
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,4 +332,7 @@ def test_messages_header(self):
332332

333333
return
334334

335-
_package_path = os.path.dirname(__file__)
335+
_package_path = os.path.dirname(__file__)
336+
337+
if __name__ == "__main__":
338+
unittest.main()

tests/searchcommands/test_validators.py

100644100755
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,6 @@ def test_file(self):
9696
self.assertRaises(ValueError, validator, path)
9797

9898
return
99+
100+
if __name__ == "__main__":
101+
unittest.main()

0 commit comments

Comments
 (0)