From a8ede1a1c05d63ac8998141689af0574b7c42ae9 Mon Sep 17 00:00:00 2001 From: Abhi Shah Date: Wed, 7 Feb 2024 16:03:52 +0530 Subject: [PATCH] Updates as per the review/feedback comments --- CHANGELOG.md | 2 +- Makefile | 6 +++--- README.md | 12 +++++------ scripts/test_specific.sh | 2 +- splunklib/binding.py | 2 +- splunklib/client.py | 5 +---- splunklib/modularinput/event.py | 4 ++-- splunklib/searchcommands/decorators.py | 2 +- splunklib/searchcommands/internals.py | 22 +++------------------ splunklib/utils.py | 12 ----------- tests/README.md | 6 ++---- tests/searchcommands/chunked_data_stream.py | 11 ++++++----- tests/test_binding.py | 3 ++- tests/test_collection.py | 16 +++++++-------- tests/test_job.py | 5 ----- tests/testlib.py | 2 +- 16 files changed, 38 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29ce30f6..ab20e262 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## Version 2.0.0-beta ### Feature updates -* `ensure_binary`, `ensure_str`, `ensure_text` and `assert_regex` utility methods have been migrated from `six.py` to `splunklib/utils.py` +* `ensure_binary`, `ensure_str` and `assert_regex` utility methods have been migrated from `six.py` to `splunklib/utils.py` ### Major changes * Removed Code specific to Python2 diff --git a/Makefile b/Makefile index 9f1bbd8b..58d53228 100644 --- a/Makefile +++ b/Makefile @@ -44,17 +44,17 @@ test_specific: .PHONY: test_smoke test_smoke: @echo "$(ATTN_COLOR)==> test_smoke $(NO_COLOR)" - @tox -e py27,py37 -- -m smoke + @tox -e py37,py39 -- -m smoke .PHONY: test_no_app test_no_app: @echo "$(ATTN_COLOR)==> test_no_app $(NO_COLOR)" - @tox -e py27,py37 -- -m "not app" + @tox -e py37,py39 -- -m "not app" .PHONY: test_smoke_no_app test_smoke_no_app: @echo "$(ATTN_COLOR)==> test_smoke_no_app $(NO_COLOR)" - @tox -e py27,py37 -- -m "smoke and not app" + @tox -e py37,py39 -- -m "smoke and not app" .PHONY: env env: diff --git a/README.md b/README.md index c9bb8cbd..dd58b446 100644 --- a/README.md +++ b/README.md @@ -25,13 +25,13 @@ The Splunk Enterprise SDK for Python contains library code, and it's examples ar Here's what you need to get going with the Splunk Enterprise SDK for Python. -* Python 2.7+ or Python 3.7. +* Python 3.7 or Python 3.9. - The Splunk Enterprise SDK for Python has been tested with Python v2.7 and v3.7. + The Splunk Enterprise SDK for Python is compatible with python3 and has been tested with Python v3.7 and v3.9. -* Splunk Enterprise 9.0 or 8.2 +* Splunk Enterprise 9.2 or 8.2 - The Splunk Enterprise SDK for Python has been tested with Splunk Enterprise 9.0, 8.2 and 8.1 + The Splunk Enterprise SDK for Python has been tested with Splunk Enterprise 9.2, 8.2 and 8.1 If you haven't already installed Splunk Enterprise, download it [here](http://www.splunk.com/download). For more information, see the Splunk Enterprise [_Installation Manual_](https://docs.splunk.com/Documentation/Splunk/latest/Installation). @@ -61,7 +61,7 @@ Install the sources you cloned from GitHub: You'll need `docker` and `docker-compose` to get up and running using this method. ``` -make up SPLUNK_VERSION=9.0 +make up SPLUNK_VERSION=9.2 make wait_up make test make down @@ -110,7 +110,7 @@ here is an example of .env file: # Access scheme (default: https) scheme=https # Your version of Splunk Enterprise - version=9.0 + version=9.2 # Bearer token for authentication #splunkToken= # Session key for authentication diff --git a/scripts/test_specific.sh b/scripts/test_specific.sh index b2890383..9f751530 100644 --- a/scripts/test_specific.sh +++ b/scripts/test_specific.sh @@ -1,4 +1,4 @@ echo "To run a specific test:" -echo " tox -e py27,py37 [test_file_path]::[TestClassName]::[test_method]" +echo " tox -e py37,py39 [test_file_path]::[TestClassName]::[test_method]" echo "For Example, To run 'test_autologin' testcase from 'test_service.py' file run" echo " tox -e py37 -- tests/test_service.py::ServiceTestCase::test_autologin" diff --git a/splunklib/binding.py b/splunklib/binding.py index 85b7038c..fcad0058 100644 --- a/splunklib/binding.py +++ b/splunklib/binding.py @@ -201,7 +201,7 @@ def __new__(self, val='', skip_encode=False, encode_slash=False): return str.__new__(self, val) if encode_slash: return str.__new__(self, parse.quote_plus(val)) - # When subclassing str, just call str.__new__ method + # When subclassing str, just call str.__new__ method # with your class and the value you want to have in the # new string. return str.__new__(self, parse.quote(val)) diff --git a/splunklib/client.py b/splunklib/client.py index c8855944..97f8b3fe 100644 --- a/splunklib/client.py +++ b/splunklib/client.py @@ -1982,9 +1982,6 @@ def delete(self, username, realm=None): :return: The `StoragePassword` collection. :rtype: ``self`` """ - if self.service.namespace.owner == '-' or self.service.namespace.app == '-': - raise ValueError("app context must be specified when removing a password.") - if realm is None: # This case makes the username optional, so # the full name can be passed in as realm. @@ -3751,7 +3748,7 @@ def update_accelerated_field(self, name, value): :return: Result of POST request """ kwargs = {} - kwargs['accelerated_fields.' + name] = value if isinstance(value, str) else json.dumps(value) + kwargs['accelerated_fields.' + name] = json.dumps(value) if isinstance(value, dict) else value return self.post(**kwargs) def update_field(self, name, value): diff --git a/splunklib/modularinput/event.py b/splunklib/modularinput/event.py index 2e398cfa..dbd9d867 100644 --- a/splunklib/modularinput/event.py +++ b/splunklib/modularinput/event.py @@ -15,7 +15,7 @@ from io import TextIOBase import xml.etree.ElementTree as ET -from splunklib.utils import ensure_text +from splunklib.utils import ensure_str class Event: @@ -105,7 +105,7 @@ def write_to(self, stream): ET.SubElement(event, "done") if isinstance(stream, TextIOBase): - stream.write(ensure_text(ET.tostring(event))) + stream.write(ensure_str(ET.tostring(event))) else: stream.write(ET.tostring(event)) stream.flush() diff --git a/splunklib/searchcommands/decorators.py b/splunklib/searchcommands/decorators.py index a3ec7abf..ae7ff6e5 100644 --- a/splunklib/searchcommands/decorators.py +++ b/splunklib/searchcommands/decorators.py @@ -15,7 +15,7 @@ # under the License. -from collections import OrderedDict # must be python 2.7 +from collections import OrderedDict from inspect import getmembers, isclass, isfunction diff --git a/splunklib/searchcommands/internals.py b/splunklib/searchcommands/internals.py index e2ccff41..65d93d20 100644 --- a/splunklib/searchcommands/internals.py +++ b/splunklib/searchcommands/internals.py @@ -40,30 +40,15 @@ def set_binary_mode(fh): """ Helper method to set up binary mode for file handles. Emphasis being sys.stdin, sys.stdout, sys.stderr. For python3, we want to return .buffer - For python2+windows we want to set os.O_BINARY """ - typefile = TextIOWrapper if sys.version_info >= (3, 0) else file + typefile = TextIOWrapper # check for file handle if not isinstance(fh, typefile): return fh - # check for python3 and buffer - if sys.version_info >= (3, 0) and hasattr(fh, 'buffer'): + # check for buffer + if hasattr(fh, 'buffer'): return fh.buffer - # check for python3 - if sys.version_info >= (3, 0): - pass - # check for windows python2. SPL-175233 -- python3 stdout is already binary - elif sys.platform == 'win32': - # Work around the fact that on Windows '\n' is mapped to '\r\n'. The typical solution is to simply open files in - # binary mode, but stdout is already open, thus this hack. 'CPython' and 'PyPy' work differently. We assume that - # all other Python implementations are compatible with 'CPython'. This might or might not be a valid assumption. - from platform import python_implementation - implementation = python_implementation() - if implementation == 'PyPy': - return os.fdopen(fh.fileno(), 'wb', 0) - import msvcrt - msvcrt.setmode(fh.fileno(), os.O_BINARY) return fh @@ -684,7 +669,6 @@ def _write_record(self, record): # We may be running under PyPy 2.5 which does not include the _json module _iterencode_json = JSONEncoder(separators=(',', ':')).iterencode else: - # Creating _iterencode_json this way yields a two-fold performance improvement on Python 2.7.9 and 2.7.10 from json.encoder import encode_basestring_ascii @staticmethod diff --git a/splunklib/utils.py b/splunklib/utils.py index 3bb80ca2..2e974999 100644 --- a/splunklib/utils.py +++ b/splunklib/utils.py @@ -44,17 +44,5 @@ def ensure_str(s, encoding='utf-8', errors='strict'): raise TypeError(f"not expecting type '{type(s)}'") -def ensure_text(s, encoding='utf-8', errors='strict'): - """ - - `str` -> `str` - - `bytes` -> decoded to `str` - """ - if isinstance(s, bytes): - return s.decode(encoding, errors) - if isinstance(s, str): - return s - raise TypeError(f"not expecting type '{type(s)}'") - - def assertRegex(self, *args, **kwargs): return getattr(self, "assertRegex")(*args, **kwargs) diff --git a/tests/README.md b/tests/README.md index 3da69c9f..da02228c 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,10 +1,8 @@ # Splunk Test Suite The test suite uses Python's standard library and the built-in **unittest** -library. If you're using Python 2.7 or Python 3.7, you're all set. However, if you are using -Python 2.6, you'll also need to install the **unittest2** library to get the -additional features that were added to Python 2.7 (just run `pip install -unittest2` or `easy_install unittest2`). +library. The Splunk Enterprise SDK for Python has been tested with Python v3.7 +and v3.9. To run the unit tests, open a command prompt in the **/splunk-sdk-python** directory and enter: diff --git a/tests/searchcommands/chunked_data_stream.py b/tests/searchcommands/chunked_data_stream.py index 39782c44..d1ac5a5f 100644 --- a/tests/searchcommands/chunked_data_stream.py +++ b/tests/searchcommands/chunked_data_stream.py @@ -4,11 +4,12 @@ import json import splunklib.searchcommands.internals +from splunklib.utils import ensure_binary, ensure_str class Chunk: def __init__(self, version, meta, data): - self.version = version + self.version = ensure_str(version) self.meta = json.loads(meta) dialect = splunklib.searchcommands.internals.CsvDialect self.data = csv.DictReader(io.StringIO(data.decode("utf-8")), @@ -20,9 +21,9 @@ def __init__(self, chunk_stream): self.chunk_stream = chunk_stream def __next__(self): - return next(self) + return self.next() - def __next__(self): + def next(self): try: return self.chunk_stream.read_chunk() except EOFError: @@ -53,7 +54,7 @@ def read_chunk(self): def build_chunk(keyval, data=None): - metadata = json.dumps(keyval).encode('utf-8') + metadata = ensure_binary(json.dumps(keyval)) data_output = _build_data_csv(data) return b"chunked 1.0,%d,%d\n%s%s" % (len(metadata), len(data_output), metadata, data_output) @@ -96,4 +97,4 @@ def _build_data_csv(data): writer.writeheader() for datum in data: writer.writerow(datum) - return csvout.getvalue().encode('utf-8') + return ensure_binary(csvout.getvalue()) diff --git a/tests/test_binding.py b/tests/test_binding.py index 4df87198..b226ef50 100755 --- a/tests/test_binding.py +++ b/tests/test_binding.py @@ -32,6 +32,7 @@ from splunklib import binding from splunklib.binding import HTTPError, AuthenticationError, UrlEncoded from splunklib import data +from splunklib.utils import ensure_str import pytest @@ -963,7 +964,7 @@ def check_response(handler): length = int(handler.headers.get('content-length', 0)) body = handler.rfile.read(length) assert handler.headers['content-type'] == 'application/x-www-form-urlencoded' - assert body.decode('utf-8') in ['baz=baf&hep=cat', 'hep=cat&baz=baf'] + assert ensure_str(body) in ['baz=baf&hep=cat', 'hep=cat&baz=baf'] with MockServer(POST=check_response): ctx = binding.connect(port=9093, scheme='http', token="waffle") diff --git a/tests/test_collection.py b/tests/test_collection.py index a92e18ea..03ec54b2 100755 --- a/tests/test_collection.py +++ b/tests/test_collection.py @@ -80,7 +80,7 @@ def test_list(self): logging.debug(f"No entities in collection {coll_name}; skipping test.") found = [ent.name for ent in coll.list()][:10] self.assertEqual(expected, found, - msg=f'on {coll_name} (expected {expected}, found {found})') + msg=f'on {coll_name} (expected: {expected}, found: {found})') def test_list_with_count(self): N = 5 @@ -111,7 +111,7 @@ def test_list_with_search(self): # TODO: DVPL-5868 - This should use a real search instead of *. Otherwise the test passes trivially. found = [ent.name for ent in coll.list(search="*")] self.assertEqual(expected, found, - msg=f'on {coll_name} (expected {expected}, found {found})') + msg=f'on {coll_name} (expected: {expected}, found: {found})') def test_list_with_sort_dir(self): for coll_name in collections: @@ -127,7 +127,7 @@ def test_list_with_sort_dir(self): found = [ent.name for ent in coll.list(**found_kwargs)] self.assertEqual(sorted(expected), sorted(found), - msg=f'on {coll_name} (expected {expected}, found {found})') + msg=f'on {coll_name} (expected: {expected}, found: {found})') def test_list_with_sort_mode_auto(self): # The jobs collection requires special handling. The sort_dir kwarg is @@ -151,7 +151,7 @@ def test_list_with_sort_mode_auto(self): else: found = [ent.name for ent in coll.list()] - self.assertEqual(expected, found, msg=f'on {coll_name} (expected {expected}, found {found})') + self.assertEqual(expected, found, msg=f'on {coll_name} (expected: {expected}, found: {found})') def test_list_with_sort_mode_alpha_case(self): for coll_name in collections: @@ -166,7 +166,7 @@ def test_list_with_sort_mode_alpha_case(self): logging.debug(f"No entities in collection {coll_name}; skipping test.") expected = sorted(found) self.assertEqual(expected, found, - msg=f'on {coll_name} (expected {expected}, found {found})') + msg=f'on {coll_name} (expected: {expected}, found: {found})') def test_list_with_sort_mode_alpha(self): for coll_name in collections: @@ -184,7 +184,7 @@ def test_list_with_sort_mode_alpha(self): logging.debug(f"No entities in collection {coll_name}; skipping test.") expected = sorted(found, key=str.lower) self.assertEqual(expected, found, - msg=f'on {coll_name} (expected {expected}, found {found})') + msg=f'on {coll_name} (expected: {expected}, found: {found})') def test_iteration(self): for coll_name in collections: @@ -197,7 +197,7 @@ def test_iteration(self): for ent in coll.iter(pagesize=max(int(total / 5.0), 1), count=10): found.append(ent.name) self.assertEqual(expected, found, - msg=f'on {coll_name} (expected {expected}, found {found})') + msg=f'on {coll_name} (expected: {expected}, found: {found})') def test_paging(self): for coll_name in collections: @@ -218,7 +218,7 @@ def test_paging(self): found.extend([ent.name for ent in page]) logging.debug("Iterate: offset=%d/%d", offset, total) self.assertEqual(expected, found, - msg=f'on {coll_name} (expected {expected}, found {found})') + msg=f'on {coll_name} (expected: {expected}, found: {found})') def test_getitem_with_nonsense(self): for coll_name in collections: diff --git a/tests/test_job.py b/tests/test_job.py index c94c8e45..8f3cef93 100755 --- a/tests/test_job.py +++ b/tests/test_job.py @@ -30,11 +30,6 @@ import pytest -# TODO: Determine if we should be importing ExpatError if ParseError is not available (e.g., on Python 2.6) -# There's code below that now catches SyntaxError instead of ParseError. Should we be catching ExpathError instead? - -# from xml.etree.ElementTree import ParseError - class TestUtilities(testlib.SDKTestCase): def test_service_search(self): diff --git a/tests/testlib.py b/tests/testlib.py index f2ca1755..79ace526 100644 --- a/tests/testlib.py +++ b/tests/testlib.py @@ -55,7 +55,7 @@ def to_bool(x): return True if x == '0': return False - raise ValueError("Not a boolean value: %s", x) + raise ValueError(f"Not a boolean value: {x}") def tmpname():