Branch was auto-updated.

This commit is contained in:
github-actions[bot]
2021-07-13 22:40:10 +00:00
committed by GitHub
27 changed files with 1668 additions and 448 deletions
Vendored Executable → Regular
+3 -2
View File
@@ -14,6 +14,7 @@
"""Python library for Splunk."""
__version_info__ = (1, 6, 2)
from __future__ import absolute_import
from splunklib.six.moves import map
__version_info__ = (1, 6, 16)
__version__ = ".".join(map(str, __version_info__))
Vendored Executable → Regular
+93 -51
View File
@@ -24,30 +24,32 @@ If you want a friendlier interface to the Splunk REST API, use the
:mod:`splunklib.client` module.
"""
import httplib
from __future__ import absolute_import
import io
import logging
import socket
import ssl
import urllib
import io
import sys
import Cookie
from base64 import b64encode
from contextlib import contextmanager
from datetime import datetime
from functools import wraps
from StringIO import StringIO
from contextlib import contextmanager
from io import BytesIO
from xml.etree.ElementTree import XML
try:
from xml.etree.ElementTree import ParseError
except ImportError, e:
from xml.parsers.expat import ExpatError as ParseError
from splunklib import six
from splunklib.six import StringIO
from splunklib.six.moves import urllib
from .data import record
try:
from xml.etree.ElementTree import ParseError
except ImportError as e:
from xml.parsers.expat import ExpatError as ParseError
__all__ = [
"AuthenticationError",
"connect",
@@ -78,6 +80,7 @@ def _parse_cookies(cookie_str, dictionary):
then updates the the dictionary with any key-value pairs found.
**Example**::
dictionary = {}
_parse_cookies('my=value', dictionary)
# Now the following is True
@@ -88,7 +91,7 @@ def _parse_cookies(cookie_str, dictionary):
:param dictionary: A dictionary to update with any found key-value pairs.
:type dictionary: ``dict``
"""
parsed_cookie = Cookie.SimpleCookie(cookie_str)
parsed_cookie = six.moves.http_cookies.SimpleCookie(cookie_str)
for cookie in parsed_cookie.values():
dictionary[cookie.key] = cookie.coded_value
@@ -168,12 +171,12 @@ class UrlEncoded(str):
elif skip_encode:
return str.__new__(self, val)
elif encode_slash:
return str.__new__(self, urllib.quote_plus(val))
return str.__new__(self, urllib.parse.quote_plus(val))
else:
# When subclassing str, just call str's __new__ method
# with your class and the value you want to have in the
# new string.
return str.__new__(self, urllib.quote(val))
return str.__new__(self, urllib.parse.quote(val))
def __add__(self, other):
"""self + other
@@ -184,7 +187,7 @@ class UrlEncoded(str):
if isinstance(other, UrlEncoded):
return UrlEncoded(str.__add__(self, other), skip_encode=True)
else:
return UrlEncoded(str.__add__(self, urllib.quote(other)), skip_encode=True)
return UrlEncoded(str.__add__(self, urllib.parse.quote(other)), skip_encode=True)
def __radd__(self, other):
"""other + self
@@ -195,7 +198,7 @@ class UrlEncoded(str):
if isinstance(other, UrlEncoded):
return UrlEncoded(str.__radd__(self, other), skip_encode=True)
else:
return UrlEncoded(str.__add__(urllib.quote(other), self), skip_encode=True)
return UrlEncoded(str.__add__(urllib.parse.quote(other), self), skip_encode=True)
def __mod__(self, fields):
"""Interpolation into ``UrlEncoded``s is disabled.
@@ -205,7 +208,7 @@ class UrlEncoded(str):
"""
raise TypeError("Cannot interpolate into a UrlEncoded object.")
def __repr__(self):
return "UrlEncoded(%s)" % repr(urllib.unquote(str(self)))
return "UrlEncoded(%s)" % repr(urllib.parse.unquote(str(self)))
@contextmanager
def _handle_auth_error(msg):
@@ -429,6 +432,8 @@ class Context(object):
:type port: ``integer``
:param scheme: The scheme for accessing the service (the default is "https").
:type scheme: "https" or "http"
:param verify: Enable (True) or disable (False) SSL verrification for https connections.
:type verify: ``Boolean``
:param sharing: The sharing mode for the namespace (the default is "user").
:type sharing: "global", "system", "app", or "user"
:param owner: The owner context of the namespace (optional, the default is "None").
@@ -445,6 +450,10 @@ class Context(object):
:type username: ``string``
:param password: The password for the Splunk account.
:type password: ``string``
:param splunkToken: Splunk authentication token
:type splunkToken: ``string``
:param headers: List of extra HTTP headers to send (optional).
:type headers: ``list`` of 2-tuples.
:param handler: The HTTP request handler (optional).
:returns: A ``Context`` instance.
@@ -461,7 +470,8 @@ class Context(object):
c = binding.Context(cookie="splunkd_8089=...")
"""
def __init__(self, handler=None, **kwargs):
self.http = HttpLib(handler)
self.http = HttpLib(handler, kwargs.get("verify", False), key_file=kwargs.get("key_file"),
cert_file=kwargs.get("cert_file")) # Default to False for backward compat
self.token = kwargs.get("token", _NoAuthenticationToken)
if self.token is None: # In case someone explicitly passes token=None
self.token = _NoAuthenticationToken
@@ -473,10 +483,12 @@ class Context(object):
self.username = kwargs.get("username", "")
self.password = kwargs.get("password", "")
self.basic = kwargs.get("basic", False)
self.bearerToken = kwargs.get("splunkToken", "")
self.autologin = kwargs.get("autologin", False)
self.additional_headers = kwargs.get("headers", [])
# Store any cookies in the self.http._cookies dict
if kwargs.has_key("cookie") and kwargs['cookie'] not in [None, _NoAuthenticationToken]:
if "cookie" in kwargs and kwargs['cookie'] not in [None, _NoAuthenticationToken]:
_parse_cookies(kwargs["cookie"], self.http._cookies)
def get_cookies(self):
@@ -508,9 +520,12 @@ class Context(object):
:returns: A list of 2-tuples containing key and value
"""
if self.has_cookies():
return [("Cookie", _make_cookie_header(self.get_cookies().items()))]
return [("Cookie", _make_cookie_header(list(self.get_cookies().items())))]
elif self.basic and (self.username and self.password):
token = 'Basic %s' % b64encode("%s:%s" % (self.username, self.password))
token = 'Basic %s' % b64encode(("%s:%s" % (self.username, self.password)).encode('utf-8')).decode('ascii')
return [("Authorization", token)]
elif self.bearerToken:
token = 'Bearer %s' % self.bearerToken
return [("Authorization", token)]
elif self.token is _NoAuthenticationToken:
return []
@@ -609,7 +624,7 @@ class Context(object):
@_authentication
@_log_duration
def get(self, path_segment, owner=None, app=None, sharing=None, **query):
def get(self, path_segment, owner=None, app=None, headers=None, sharing=None, **query):
"""Performs a GET operation from the REST path segment with the given
namespace and query.
@@ -632,6 +647,8 @@ class Context(object):
:type owner: ``string``
:param app: The app context of the namespace (optional).
:type app: ``string``
:param headers: List of extra HTTP headers to send (optional).
:type headers: ``list`` of 2-tuples.
:param sharing: The sharing mode of the namespace (optional).
:type sharing: ``string``
:param query: All other keyword arguments, which are used as query
@@ -659,10 +676,14 @@ class Context(object):
c.logout()
c.get('apps/local') # raises AuthenticationError
"""
if headers is None:
headers = []
path = self.authority + self._abspath(path_segment, owner=owner,
app=app, sharing=sharing)
logging.debug("GET request to %s (body: %s)", path, repr(query))
response = self.http.get(path, self._auth_headers, **query)
all_headers = headers + self.additional_headers + self._auth_headers
response = self.http.get(path, all_headers, **query)
return response
@_authentication
@@ -703,7 +724,12 @@ class Context(object):
:type headers: ``list`` of 2-tuples.
:param query: All other keyword arguments, which are used as query
parameters.
:type query: ``string``
:param body: Parameters to be used in the post body. If specified,
any parameters in the query will be applied to the URL instead of
the body. If a dict is supplied, the key-value pairs will be form
encoded. If a string is supplied, the body will be passed through
in the request unchanged.
:type body: ``dict`` or ``str``
:return: The response from the server.
:rtype: ``dict`` with keys ``body``, ``headers``, ``reason``,
and ``status``
@@ -734,7 +760,7 @@ class Context(object):
path = self.authority + self._abspath(path_segment, owner=owner, app=app, sharing=sharing)
logging.debug("POST request to %s (body: %s)", path, repr(query))
all_headers = headers + self._auth_headers
all_headers = headers + self.additional_headers + self._auth_headers
response = self.http.post(path, all_headers, **query)
return response
@@ -800,7 +826,7 @@ class Context(object):
path = self.authority \
+ self._abspath(path_segment, owner=owner,
app=app, sharing=sharing)
all_headers = headers + self._auth_headers
all_headers = headers + self.additional_headers + self._auth_headers
logging.debug("%s request to %s (headers: %s, body: %s)",
method, path, str(all_headers), repr(body))
response = self.http.request(path,
@@ -848,12 +874,17 @@ class Context(object):
# as credentials were passed in.
return
if self.bearerToken:
# Bearer auth mode requested, so this method is a nop as long
# as authentication token was passed in.
return
# Only try to get a token and updated cookie if username & password are specified
try:
response = self.http.post(
self.authority + self._abspath("/services/auth/login"),
username=self.username,
password=self.password,
headers=self.additional_headers,
cookie="1") # In Splunk 6.2+, passing "cookie=1" will return the "set-cookie" header
body = response.body.read()
@@ -964,6 +995,8 @@ def connect(**kwargs):
:type username: ``string``
:param password: The password for the Splunk account.
:type password: ``string``
:param headers: List of extra HTTP headers to send (optional).
:type headers: ``list`` of 2-tuples.
:param autologin: When ``True``, automatically tries to log in again if the
session terminates.
:type autologin: ``Boolean``
@@ -1011,7 +1044,7 @@ class AuthenticationError(HTTPError):
def __init__(self, message, cause):
# Put the body back in the response so that HTTPError's constructor can
# read it again.
cause._response.body = StringIO(cause.body)
cause._response.body = BytesIO(cause.body)
HTTPError.__init__(self, cause._response, message)
@@ -1042,22 +1075,23 @@ class AuthenticationError(HTTPError):
# 'foo=1&foo=2&foo=3'.
def _encode(**kwargs):
items = []
for key, value in kwargs.iteritems():
for key, value in six.iteritems(kwargs):
if isinstance(value, list):
items.extend([(key, item) for item in value])
else:
items.append((key, value))
return urllib.urlencode(items)
return urllib.parse.urlencode(items)
# Crack the given url into (scheme, host, port, path)
def _spliturl(url):
scheme, opaque = urllib.splittype(url)
netloc, path = urllib.splithost(opaque)
host, port = urllib.splitport(netloc)
parsed_url = urllib.parse.urlparse(url)
host = parsed_url.hostname
port = parsed_url.port
path = '?'.join((parsed_url.path, parsed_url.query)) if parsed_url.query else parsed_url.path
# Strip brackets if its an IPv6 address
if host.startswith('[') and host.endswith(']'): host = host[1:-1]
if port is None: port = DEFAULT_PORT
return scheme, host, port, path
return parsed_url.scheme, host, port, path
# Given an HTTP request handler, this wrapper objects provides a related
# family of convenience methods built using that handler.
@@ -1100,9 +1134,14 @@ class HttpLib(object):
The response dictionary is returned directly by ``HttpLib``'s methods with
no further processing. By default, ``HttpLib`` calls the :func:`handler` function
to get a handler function.
If using the default handler, SSL verification can be disabled by passing verify=False.
"""
def __init__(self, custom_handler=None):
self.handler = handler() if custom_handler is None else custom_handler
def __init__(self, custom_handler=None, verify=False, key_file=None, cert_file=None):
if custom_handler is None:
self.handler = handler(verify=verify, key_file=key_file, cert_file=cert_file)
else:
self.handler = custom_handler
self._cookies = {}
def delete(self, url, headers=None, **kwargs):
@@ -1185,14 +1224,16 @@ class HttpLib(object):
# We only use application/x-www-form-urlencoded if there is no other
# Content-Type header present. This can happen in cases where we
# send requests as application/json, e.g. for KV Store.
if len(filter(lambda x: x[0].lower() == "content-type", headers)) == 0:
if len([x for x in headers if x[0].lower() == "content-type"]) == 0:
headers.append(("Content-Type", "application/x-www-form-urlencoded"))
body = kwargs.pop('body')
if isinstance(body, dict):
body = _encode(**body).encode('utf-8')
if len(kwargs) > 0:
url = url + UrlEncoded('?' + _encode(**kwargs), skip_encode=True)
else:
body = _encode(**kwargs)
body = _encode(**kwargs).encode('utf-8')
message = {
'method': "POST",
'headers': headers,
@@ -1226,7 +1267,7 @@ class HttpLib(object):
# If response.headers is a dict, get the key-value pairs as 2-tuples
# this is the case when using urllib2
if isinstance(response.headers, dict):
key_value_tuples = response.headers.items()
key_value_tuples = list(response.headers.items())
for key, value in key_value_tuples:
if key.lower() == "set-cookie":
_parse_cookies(value, self._cookies)
@@ -1248,7 +1289,7 @@ class ResponseReader(io.RawIOBase):
def __init__(self, response, connection=None):
self._response = response
self._connection = connection
self._buffer = ''
self._buffer = b''
def __str__(self):
return self.read()
@@ -1256,7 +1297,7 @@ class ResponseReader(io.RawIOBase):
@property
def empty(self):
"""Indicates whether there is any more data in the response."""
return self.peek(1) == ""
return self.peek(1) == b""
def peek(self, size):
"""Nondestructively retrieves a given number of characters.
@@ -1273,8 +1314,8 @@ class ResponseReader(io.RawIOBase):
def close(self):
"""Closes this response."""
if _connection:
_connection.close()
if self._connection:
self._connection.close()
self._response.close()
def read(self, size = None):
@@ -1286,7 +1327,7 @@ class ResponseReader(io.RawIOBase):
"""
r = self._buffer
self._buffer = ''
self._buffer = b''
if size is not None:
size -= len(r)
r = r + self._response.read(size)
@@ -1310,7 +1351,7 @@ class ResponseReader(io.RawIOBase):
return bytes_read
def handler(key_file=None, cert_file=None, timeout=None):
def handler(key_file=None, cert_file=None, timeout=None, verify=False):
"""This class returns an instance of the default HTTP request handler using
the values you provide.
@@ -1320,21 +1361,22 @@ def handler(key_file=None, cert_file=None, timeout=None):
:type cert_file: ``string``
:param `timeout`: The request time-out period, in seconds (optional).
:type timeout: ``integer`` or "None"
:param `verify`: Set to False to disable SSL verification on https connections.
:type verify: ``Boolean``
"""
def connect(scheme, host, port):
kwargs = {}
if timeout is not None: kwargs['timeout'] = timeout
if scheme == "http":
return httplib.HTTPConnection(host, port, **kwargs)
return six.moves.http_client.HTTPConnection(host, port, **kwargs)
if scheme == "https":
if key_file is not None: kwargs['key_file'] = key_file
if cert_file is not None: kwargs['cert_file'] = cert_file
# If running Python 2.7.9+, disable SSL certificate validation
if sys.version_info >= (2,7,9) and key_file is None and cert_file is None:
if not verify:
kwargs['context'] = ssl._create_unverified_context()
return httplib.HTTPSConnection(host, port, **kwargs)
return six.moves.http_client.HTTPSConnection(host, port, **kwargs)
raise ValueError("unsupported scheme: %s" % scheme)
def request(url, message, **kwargs):
@@ -1343,7 +1385,7 @@ def handler(key_file=None, cert_file=None, timeout=None):
head = {
"Content-Length": str(len(body)),
"Host": host,
"User-Agent": "splunk-sdk-python/1.6.2",
"User-Agent": "splunk-sdk-python/1.6.16",
"Accept": "*/*",
"Connection": "Close",
} # defaults
Vendored Executable → Regular
+66 -47
View File
@@ -58,18 +58,22 @@ attributes, and methods that are specific to each kind of entity. For example::
my_app.package() # Creates a compressed package of this application
"""
import contextlib
import datetime
import json
import urllib
import logging
from time import sleep
from datetime import datetime, timedelta
import socket
import contextlib
from datetime import datetime, timedelta
from time import sleep
from splunklib import six
from splunklib.six.moves import urllib
from .binding import Context, HTTPError, AuthenticationError, namespace, UrlEncoded, _encode, _make_cookie_header, _NoAuthenticationToken
from .data import record
from . import data
from .binding import (AuthenticationError, Context, HTTPError, UrlEncoded,
_encode, _make_cookie_header, _NoAuthenticationToken,
namespace)
from .data import record
__all__ = [
"connect",
@@ -182,7 +186,7 @@ def _trailing(template, *targets):
def _filter_content(content, *args):
if len(args) > 0:
return record((k, content[k]) for k in args)
return record((k, v) for k, v in content.iteritems()
return record((k, v) for k, v in six.iteritems(content)
if k not in ['eai:acl', 'eai:attributes', 'type'])
# Construct a resource path from the given base path + resource name
@@ -192,8 +196,11 @@ def _path(base, name):
# Load an atom record from the body of the given response
# this will ultimately be sent to an xml ElementTree so we
# should use the xmlcharrefreplace option
def _load_atom(response, match=None):
return data.load(response.body.read(), match)
return data.load(response.body.read()
.decode('utf-8', 'xmlcharrefreplace'), match)
# Load an array of atom entries from the body of the given response
@@ -236,7 +243,7 @@ def _parse_atom_entry(entry):
metadata = _parse_atom_metadata(content)
# Filter some of the noise out of the content record
content = record((k, v) for k, v in content.iteritems()
content = record((k, v) for k, v in six.iteritems(content)
if k not in ['eai:acl', 'eai:attributes'])
if 'type' in content:
@@ -288,6 +295,9 @@ def connect(**kwargs):
:type port: ``integer``
:param scheme: The scheme for accessing the service (the default is "https").
:type scheme: "https" or "http"
:param verify: Enable (True) or disable (False) SSL verrification for
https connections. (optional, the default is True)
:type verify: ``Boolean``
:param `owner`: The owner context of the namespace (optional).
:type owner: ``string``
:param `app`: The app context of the namespace (optional).
@@ -355,6 +365,9 @@ class Service(_BaseService):
:type port: ``integer``
:param scheme: The scheme for accessing the service (the default is "https").
:type scheme: "https" or "http"
:param verify: Enable (True) or disable (False) SSL verrification for
https connections. (optional, the default is True)
:type verify: ``Boolean``
:param `owner`: The owner context of the namespace (optional; use "-" for wildcard).
:type owner: ``string``
:param `app`: The app context of the namespace (optional; use "-" for wildcard).
@@ -450,6 +463,13 @@ class Service(_BaseService):
response = self.get("/services/server/info")
return _filter_content(_load_atom(response, MATCH_ENTRY_CONTENT))
def input(self, path, kind=None):
"""Retrieves an input by path, and optionally kind.
:return: A :class:`Input` object.
"""
return Input(self, path, kind=kind).refresh()
@property
def inputs(self):
"""Returns the collection of inputs configured on this Splunk instance.
@@ -559,9 +579,9 @@ class Service(_BaseService):
self.login()
if not self.restart_required:
return result
except Exception, e:
except Exception as e:
sleep(1)
raise Exception, "Operation time out."
raise Exception("Operation time out.")
@property
def restart_required(self):
@@ -884,7 +904,7 @@ class Entity(Endpoint):
try:
self[item]
return True
except KeyError, AttributeError:
except (KeyError, AttributeError):
return False
def __eq__(self, other):
@@ -1081,7 +1101,7 @@ class Entity(Endpoint):
# text to be dispatched via HTTP. However, these links are already
# URL encoded when they arrive, and we need to mark them as such.
unquoted_links = dict([(k, UrlEncoded(v, skip_encode=True))
for k,v in results['links'].iteritems()])
for k,v in six.iteritems(results['links'])])
results['links'] = unquoted_links
return results
@@ -1187,7 +1207,7 @@ class ReadOnlyCollection(Endpoint):
:raises ValueError: Raised if no namespace is specified and *key*
does not refer to a unique name.
*Example*::
**Example**::
s = client.connect(...)
saved_searches = s.saved_searches
@@ -1290,7 +1310,7 @@ class ReadOnlyCollection(Endpoint):
# This has been factored out so that it can be easily
# overloaded by Configurations, which has to switch its
# entities' endpoints from its own properties/ to configs/.
raw_path = urllib.unquote(state.links.alternate)
raw_path = urllib.parse.unquote(state.links.alternate)
if 'servicesNS/' in raw_path:
return _trailing(raw_path, 'servicesNS/', '/', '/')
elif 'services/' in raw_path:
@@ -1534,7 +1554,7 @@ class Collection(ReadOnlyCollection):
applications = s.apps
new_app = applications.create("my_fake_app")
"""
if not isinstance(name, basestring):
if not isinstance(name, six.string_types):
raise InvalidNameException("%s is not a valid name for an entity." % name)
if 'namespace' in params:
namespace = params.pop('namespace')
@@ -1623,9 +1643,9 @@ class Collection(ReadOnlyCollection):
:rtype: ``dict`` with keys ``body``, ``headers``, ``reason``,
and ``status``
Example:
**Example**::
import splunklib.client
import splunklib.client
s = client.service(...)
saved_searches = s.saved_searches
saved_searches.get("my/saved/search") == \\
@@ -1717,7 +1737,7 @@ class Configurations(Collection):
# This has to be overridden to handle the plumbing of creating
# a ConfigurationFile (which is a Collection) instead of some
# Entity.
if not isinstance(name, basestring):
if not isinstance(name, six.string_types):
raise ValueError("Invalid name: %s" % repr(name))
response = self.post(__conf=name)
if response.status == 303:
@@ -1811,7 +1831,7 @@ class StoragePasswords(Collection):
:return: The :class:`StoragePassword` object created.
"""
if not isinstance(username, basestring):
if not isinstance(username, six.string_types):
raise ValueError("Invalid name: %s" % repr(username))
if realm is None:
@@ -1852,7 +1872,7 @@ class StoragePasswords(Collection):
name = UrlEncoded(realm, encode_slash=True) + ":" + UrlEncoded(username, encode_slash=True)
# Append the : expected at the end of the name
if name[-1] is not ":":
if name[-1] != ":":
name = name + ":"
return Collection.delete(self, name)
@@ -1935,7 +1955,7 @@ class Index(Entity):
if host is not None: args['host'] = host
if source is not None: args['source'] = source
if sourcetype is not None: args['sourcetype'] = sourcetype
path = UrlEncoded(PATH_RECEIVERS_STREAM + "?" + urllib.urlencode(args), skip_encode=True)
path = UrlEncoded(PATH_RECEIVERS_STREAM + "?" + urllib.parse.urlencode(args), skip_encode=True)
cookie_or_auth_header = "Authorization: Splunk %s\r\n" % \
(self.service.token if self.service.token is _NoAuthenticationToken
@@ -1949,12 +1969,12 @@ class Index(Entity):
# the connection open and use the Splunk extension headers to note
# the input mode
sock = self.service.connect()
headers = ["POST %s HTTP/1.1\r\n" % self.service._abspath(path),
"Host: %s:%s\r\n" % (self.service.host, int(self.service.port)),
"Accept-Encoding: identity\r\n",
cookie_or_auth_header,
"X-Splunk-Input-Mode: Streaming\r\n",
"\r\n"]
headers = [("POST %s HTTP/1.1\r\n" % str(self.service._abspath(path))).encode('utf-8'),
("Host: %s:%s\r\n" % (self.service.host, int(self.service.port))).encode('utf-8'),
b"Accept-Encoding: identity\r\n",
cookie_or_auth_header.encode('utf-8'),
b"X-Splunk-Input-Mode: Streaming\r\n",
b"\r\n"]
for h in headers:
sock.write(h)
@@ -2028,8 +2048,7 @@ class Index(Entity):
self.refresh()
if self.content.totalEventCount != '0':
raise OperationError, "Cleaning index %s took longer than %s seconds; timing out." %\
(self.name, timeout)
raise OperationError("Cleaning index %s took longer than %s seconds; timing out." % (self.name, timeout))
finally:
# Restore original values
self.update(maxTotalDataSizeMB=tds, frozenTimePeriodInSecs=ftp)
@@ -2297,7 +2316,7 @@ class Inputs(Collection):
path = _path(
self.path + kindpath,
'%s:%s' % (kwargs['restrictToHost'], name) \
if kwargs.has_key('restrictToHost') else name
if 'restrictToHost' in kwargs else name
)
return Input(self.service, path, kind)
@@ -2504,7 +2523,7 @@ class Inputs(Collection):
try:
path = UrlEncoded(path, skip_encode=True)
response = self.get(path, **kwargs)
except HTTPError, he:
except HTTPError as he:
if he.status == 404: # No inputs of this kind
return []
entities = []
@@ -2516,7 +2535,7 @@ class Inputs(Collection):
# Unquote the URL, since all URL encoded in the SDK
# should be of type UrlEncoded, and all str should not
# be URL encoded.
path = urllib.unquote(state.links.alternate)
path = urllib.parse.unquote(state.links.alternate)
entity = Input(self.service, path, kind, state=state)
entities.append(entity)
return entities
@@ -2542,7 +2561,7 @@ class Inputs(Collection):
# Unquote the URL, since all URL encoded in the SDK
# should be of type UrlEncoded, and all str should not
# be URL encoded.
path = urllib.unquote(state.links.alternate)
path = urllib.parse.unquote(state.links.alternate)
entity = Input(self.service, path, kind, state=state)
entities.append(entity)
if 'offset' in kwargs:
@@ -3359,7 +3378,7 @@ class Users(Collection):
boris = users.create("boris", "securepassword", roles="user")
hilda = users.create("hilda", "anotherpassword", roles=["user","power"])
"""
if not isinstance(username, basestring):
if not isinstance(username, six.string_types):
raise ValueError("Invalid username: %s" % str(username))
username = username.lower()
self.post(name=username, password=password, roles=roles, **params)
@@ -3370,7 +3389,7 @@ class Users(Collection):
state = _parse_atom_entry(entry)
entity = self.item(
self.service,
urllib.unquote(state.links.alternate),
urllib.parse.unquote(state.links.alternate),
state=state)
return entity
@@ -3482,7 +3501,7 @@ class Roles(Collection):
roles = c.roles
paltry = roles.create("paltry", imported_roles="user", defaultApp="search")
"""
if not isinstance(name, basestring):
if not isinstance(name, six.string_types):
raise ValueError("Invalid role name: %s" % str(name))
name = name.lower()
self.post(name=name, **params)
@@ -3493,7 +3512,7 @@ class Roles(Collection):
state = _parse_atom_entry(entry)
entity = self.item(
self.service,
urllib.unquote(state.links.alternate),
urllib.parse.unquote(state.links.alternate),
state=state)
return entity
@@ -3544,11 +3563,11 @@ class KVStoreCollections(Collection):
:return: Result of POST request
"""
for k, v in indexes.iteritems():
for k, v in six.iteritems(indexes):
if isinstance(v, dict):
v = json.dumps(v)
kwargs['index.' + k] = v
for k, v in fields.iteritems():
for k, v in six.iteritems(fields):
kwargs['field.' + k] = v
return self.post(name=name, **kwargs)
@@ -3557,7 +3576,7 @@ class KVStoreCollection(Entity):
def data(self):
"""Returns data object for this Collection.
:rtype: :class:`KVStoreData`
:rtype: :class:`KVStoreCollectionData`
"""
return KVStoreCollectionData(self)
@@ -3621,7 +3640,7 @@ class KVStoreCollectionData(object):
:return: Array of documents retrieved by query.
:rtype: ``array``
"""
return json.loads(self._get('', **query).body.read())
return json.loads(self._get('', **query).body.read().decode('utf-8'))
def query_by_id(self, id):
"""
@@ -3633,7 +3652,7 @@ class KVStoreCollectionData(object):
:return: Document with id
:rtype: ``dict``
"""
return json.loads(self._get(UrlEncoded(str(id))).body.read())
return json.loads(self._get(UrlEncoded(str(id))).body.read().decode('utf-8'))
def insert(self, data):
"""
@@ -3645,7 +3664,7 @@ class KVStoreCollectionData(object):
:return: _id of inserted object
:rtype: ``dict``
"""
return json.loads(self._post('', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read())
return json.loads(self._post('', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8'))
def delete(self, query=None):
"""
@@ -3681,7 +3700,7 @@ class KVStoreCollectionData(object):
:return: id of replaced document
:rtype: ``dict``
"""
return json.loads(self._post(UrlEncoded(str(id)), headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read())
return json.loads(self._post(UrlEncoded(str(id)), headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8'))
def batch_find(self, *dbqueries):
"""
@@ -3698,7 +3717,7 @@ class KVStoreCollectionData(object):
data = json.dumps(dbqueries)
return json.loads(self._post('batch_find', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read())
return json.loads(self._post('batch_find', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8'))
def batch_save(self, *documents):
"""
@@ -3715,4 +3734,4 @@ class KVStoreCollectionData(object):
data = json.dumps(documents)
return json.loads(self._post('batch_save', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read())
return json.loads(self._post('batch_save', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8'))
Vendored Executable → Regular
+37 -29
View File
@@ -12,11 +12,14 @@
# License for the specific language governing permissions and limitations
# under the License.
"""The **splunklib.data** module reads the responses from splunkd in Atom Feed
"""The **splunklib.data** module reads the responses from splunkd in Atom Feed
format, which is the format used by most of the REST API.
"""
from __future__ import absolute_import
import sys
from xml.etree.ElementTree import XML
from splunklib import six
__all__ = ["load"]
@@ -56,10 +59,10 @@ def localname(xname):
return xname if rcurly == -1 else xname[rcurly+1:]
def load(text, match=None):
"""This function reads a string that contains the XML of an Atom Feed, then
returns the
data in a native Python structure (a ``dict`` or ``list``). If you also
provide a tag name or path to match, only the matching sub-elements are
"""This function reads a string that contains the XML of an Atom Feed, then
returns the
data in a native Python structure (a ``dict`` or ``list``). If you also
provide a tag name or path to match, only the matching sub-elements are
loaded.
:param text: The XML text to load.
@@ -74,12 +77,17 @@ def load(text, match=None):
'namespaces': [],
'names': {}
}
# Convert to unicode encoding in only python 2 for xml parser
if(sys.version_info < (3, 0, 0) and isinstance(text, unicode)):
text = text.encode('utf-8')
root = XML(text)
items = [root] if match is None else root.findall(match)
count = len(items)
if count == 0:
if count == 0:
return None
elif count == 1:
elif count == 1:
return load_root(items[0], nametable)
else:
return [load_root(item, nametable) for item in items]
@@ -88,7 +96,7 @@ def load(text, match=None):
def load_attrs(element):
if not hasattrs(element): return None
attrs = record()
for key, value in element.attrib.iteritems():
for key, value in six.iteritems(element.attrib):
attrs[key] = value
return attrs
@@ -110,12 +118,12 @@ def load_elem(element, nametable=None):
if attrs is None: return name, value
if value is None: return name, attrs
# If value is simple, merge into attrs dict using special key
if isinstance(value, str):
if isinstance(value, six.string_types):
attrs["$text"] = value
return name, attrs
# Both attrs & value are complex, so merge the two dicts, resolving collisions.
collision_keys = []
for key, val in attrs.iteritems():
for key, val in six.iteritems(attrs):
if key in value and key in collision_keys:
value[key].append(val)
elif key in value and key not in collision_keys:
@@ -151,10 +159,10 @@ def load_value(element, nametable=None):
# No children, assume a simple text value
if count == 0:
text = element.text
if text is None:
if text is None:
return None
text = text.strip()
if len(text) == 0:
if len(text) == 0:
return None
return text
@@ -169,9 +177,9 @@ def load_value(element, nametable=None):
for child in children:
name, item = load_elem(child, nametable)
# If we have seen this name before, promote the value to a list
if value.has_key(name):
if name in value:
current = value[name]
if not isinstance(current, list):
if not isinstance(current, list):
value[name] = [current]
value[name].append(item)
else:
@@ -181,21 +189,21 @@ def load_value(element, nametable=None):
# A generic utility that enables "dot" access to dicts
class Record(dict):
"""This generic utility class enables dot access to members of a Python
"""This generic utility class enables dot access to members of a Python
dictionary.
Any key that is also a valid Python identifier can be retrieved as a field.
So, for an instance of ``Record`` called ``r``, ``r.key`` is equivalent to
``r['key']``. A key such as ``invalid-key`` or ``invalid.key`` cannot be
retrieved as a field, because ``-`` and ``.`` are not allowed in
Any key that is also a valid Python identifier can be retrieved as a field.
So, for an instance of ``Record`` called ``r``, ``r.key`` is equivalent to
``r['key']``. A key such as ``invalid-key`` or ``invalid.key`` cannot be
retrieved as a field, because ``-`` and ``.`` are not allowed in
identifiers.
Keys of the form ``a.b.c`` are very natural to write in Python as fields. If
a group of keys shares a prefix ending in ``.``, you can retrieve keys as a
Keys of the form ``a.b.c`` are very natural to write in Python as fields. If
a group of keys shares a prefix ending in ``.``, you can retrieve keys as a
nested dictionary by calling only the prefix. For example, if ``r`` contains
keys ``'foo'``, ``'bar.baz'``, and ``'bar.qux'``, ``r.bar`` returns a record
with the keys ``baz`` and ``qux``. If a key contains multiple ``.``, each
one is placed into a nested dictionary, so you can write ``r.bar.qux`` or
with the keys ``baz`` and ``qux``. If a key contains multiple ``.``, each
one is placed into a nested dictionary, so you can write ``r.bar.qux`` or
``r['bar.qux']`` interchangeably.
"""
sep = '.'
@@ -207,7 +215,7 @@ class Record(dict):
def __getattr__(self, name):
try:
return self[name]
except KeyError:
except KeyError:
raise AttributeError(name)
def __delattr__(self, name):
@@ -227,7 +235,7 @@ class Record(dict):
return dict.__getitem__(self, key)
key += self.sep
result = record()
for k,v in self.iteritems():
for k,v in six.iteritems(self):
if not k.startswith(key):
continue
suffix = k[len(key):]
@@ -244,12 +252,12 @@ class Record(dict):
if len(result) == 0:
raise KeyError("No key or prefix: %s" % key)
return result
def record(value=None):
"""This function returns a :class:`Record` instance constructed with an
def record(value=None):
"""This function returns a :class:`Record` instance constructed with an
initial value that you provide.
:param `value`: An initial record value.
:type `value`: ``dict``
"""
View File
+4 -3
View File
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
try:
import xml.etree.ElementTree as ET
except ImportError:
@@ -20,7 +21,7 @@ except ImportError:
class Argument(object):
"""Class representing an argument to a modular input kind.
``Argument`` is meant to be used with ``Scheme`` to generate an XML
``Argument`` is meant to be used with ``Scheme`` to generate an XML
definition of the modular input kind that Splunk understands.
``name`` is the only required parameter for the constructor.
@@ -53,9 +54,9 @@ class Argument(object):
:param name: ``string``, identifier for this argument in Splunk.
:param description: ``string``, human-readable description of the argument.
:param validation: ``string`` specifying how the argument should be validated, if using internal validation.
If using external validation, this will be ignored.
If using external validation, this will be ignored.
:param data_type: ``string``, data type of this field; use the class constants.
"data_type_boolean", "data_type_number", or "data_type_string".
"data_type_boolean", "data_type_number", or "data_type_string".
:param required_on_edit: ``Boolean``, whether this arg is required when editing an existing modular input of this kind.
:param required_on_create: ``Boolean``, whether this arg is required when creating a modular input of this kind.
:param title: ``String``, a human-readable title for the argument.
Vendored Executable → Regular
+8 -1
View File
@@ -12,6 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
from io import TextIOBase
from splunklib.six import ensure_text
try:
import xml.etree.cElementTree as ET
except ImportError as ie:
@@ -103,5 +107,8 @@ class Event(object):
if self.done:
ET.SubElement(event, "done")
stream.write(ET.tostring(event))
if isinstance(stream, TextIOBase):
stream.write(ensure_text(ET.tostring(event)))
else:
stream.write(ET.tostring(event))
stream.flush()
+8 -5
View File
@@ -12,18 +12,20 @@
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
import sys
from io import TextIOWrapper, TextIOBase
from splunklib.six import ensure_str
from .event import ET
try:
from cStringIO import StringIO
from splunklib.six.moves import cStringIO as StringIO
except ImportError:
from StringIO import StringIO
from splunklib.six import StringIO
class EventWriter(object):
"""``EventWriter`` writes events and error messages to Splunk from a modular input.
Its two important methods are ``writeEvent``, which takes an ``Event`` object,
and ``log``, which takes a severity and an error message.
"""
@@ -76,9 +78,10 @@ class EventWriter(object):
:param document: An ``ElementTree`` object.
"""
self._out.write(ET.tostring(document))
self._out.write(ensure_str(ET.tostring(document)))
self._out.flush()
def close(self):
"""Write the closing </stream> tag to make this XML well formed."""
self._out.write("</stream>")
self._out.write("</stream>")
self._out.flush()
+1
View File
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
try:
import xml.etree.cElementTree as ET
except ImportError as ie:
Vendored Executable → Regular
+2 -1
View File
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
try:
import xml.etree.cElementTree as ET
except ImportError:
@@ -54,7 +55,7 @@ class Scheme(object):
def to_xml(self):
"""Creates an ``ET.Element`` representing self, then returns it.
:returns root, an ``ET.Element`` representing this scheme.
:returns: an ``ET.Element`` representing this scheme.
"""
root = ET.Element("scheme")
Vendored Executable → Regular
+9 -8
View File
@@ -12,14 +12,16 @@
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
from abc import ABCMeta, abstractmethod
from urlparse import urlsplit
from splunklib.six.moves.urllib.parse import urlsplit
import sys
from ..client import Service
from .event_writer import EventWriter
from .input_definition import InputDefinition
from .validation_definition import ValidationDefinition
from splunklib import six
try:
import xml.etree.cElementTree as ET
@@ -27,7 +29,7 @@ except ImportError:
import xml.etree.ElementTree as ET
class Script(object):
class Script(six.with_metaclass(ABCMeta, object)):
"""An abstract base class for implementing modular inputs.
Subclasses should override ``get_scheme``, ``stream_events``,
@@ -37,7 +39,6 @@ class Script(object):
The ``run`` function is used to run modular inputs; it typically should
not be overridden.
"""
__metaclass__ = ABCMeta
def __init__(self):
self._input_definition = None
@@ -101,10 +102,10 @@ class Script(object):
err_string = "ERROR Invalid arguments to modular input script:" + ' '.join(
args)
event_writer._err.write(err_string)
return 1
except Exception as e:
err_string = EventWriter.ERROR + str(e.message)
event_writer._err.write(err_string)
event_writer.log(EventWriter.ERROR, str(e))
return 1
@property
@@ -116,9 +117,9 @@ class Script(object):
available as soon as the :code:`Script.stream_events` method is
called.
:return: :class:splunklib.client.Service. A value of None is returned,
if you call this method before the :code:`Script.stream_events` method
is called.
:return: :class:`splunklib.client.Service`. A value of None is returned,
if you call this method before the :code:`Script.stream_events` method
is called.
"""
if self._service is not None:
Vendored Executable → Regular
+3 -1
View File
@@ -14,6 +14,8 @@
# File for utility functions
from __future__ import absolute_import
from splunklib.six.moves import zip
def xml_compare(expected, found):
"""Checks equality of two ``ElementTree`` objects.
@@ -69,4 +71,4 @@ def parse_xml_data(parent_node, child_node_tag):
data[child.get("name")][param.get("name")] = parse_parameters(param)
elif "item" == parent_node.tag:
data[child.get("name")] = parse_parameters(child)
return data
return data
+19 -16
View File
@@ -13,6 +13,7 @@
# under the License.
from __future__ import absolute_import
try:
import xml.etree.cElementTree as ET
except ImportError as ie:
@@ -27,7 +28,7 @@ class ValidationDefinition(object):
**Example**::
``v = ValidationDefinition()``
v = ValidationDefinition()
"""
def __init__(self):
@@ -45,23 +46,25 @@ class ValidationDefinition(object):
The XML typically will look like this:
``<items>``
`` <server_host>myHost</server_host>``
`` <server_uri>https://127.0.0.1:8089</server_uri>``
`` <session_key>123102983109283019283</session_key>``
`` <checkpoint_dir>/opt/splunk/var/lib/splunk/modinputs</checkpoint_dir>``
`` <item name="myScheme">``
`` <param name="param1">value1</param>``
`` <param_list name="param2">``
`` <value>value2</value>``
`` <value>value3</value>``
`` <value>value4</value>``
`` </param_list>``
`` </item>``
``</items>``
.. code-block:: xml
<items>
<server_host>myHost</server_host>
<server_uri>https://127.0.0.1:8089</server_uri>
<session_key>123102983109283019283</session_key>
<checkpoint_dir>/opt/splunk/var/lib/splunk/modinputs</checkpoint_dir>
<item name="myScheme">
<param name="param1">value1</param>
<param_list name="param2">
<value>value2</value>
<value>value3</value>
<value>value4</value>
</param_list>
</item>
</items>
:param stream: ``Stream`` containing XML to parse.
:return definition: A ``ValidationDefinition`` object.
:return: A ``ValidationDefinition`` object.
"""
Vendored Executable → Regular
View File
Vendored Executable → Regular
+22 -15
View File
@@ -32,6 +32,11 @@ as follows:::
print "Results are a preview: %s" % reader.is_preview
"""
from __future__ import absolute_import
from io import BytesIO
from splunklib import six
try:
import xml.etree.cElementTree as et
except:
@@ -43,9 +48,9 @@ except ImportError:
from .ordereddict import OrderedDict
try:
from cStringIO import StringIO
from splunklib.six.moves import cStringIO as StringIO
except:
from StringIO import StringIO
from splunklib.six import StringIO
__all__ = [
"ResultsReader",
@@ -95,13 +100,13 @@ class _ConcatenatedStream(object):
If *n* is ``None``, return all available characters.
"""
response = ""
response = b""
while len(self.streams) > 0 and (n is None or n > 0):
txt = self.streams[0].read(n)
response += txt
if n is not None:
n -= len(txt)
if n > 0 or n is None:
if n is None or n > 0:
del self.streams[0]
return response
@@ -126,17 +131,17 @@ class _XMLDTDFilter(object):
If *n* is ``None``, return all available characters.
"""
response = ""
response = b""
while n is None or n > 0:
c = self.stream.read(1)
if c == "":
if c == b"":
break
elif c == "<":
elif c == b"<":
c += self.stream.read(1)
if c == "<?":
if c == b"<?":
while True:
q = self.stream.read(1)
if q == ">":
if q == b">":
break
else:
response += c
@@ -194,7 +199,7 @@ class ResultsReader(object):
# we remove all the DTD definitions inline, then wrap the
# fragments in a fiction <doc> element to make the parser happy.
stream = _XMLDTDFilter(stream)
stream = _ConcatenatedStream(StringIO("<doc>"), stream, StringIO("</doc>"))
stream = _ConcatenatedStream(BytesIO(b"<doc>"), stream, BytesIO(b"</doc>"))
self.is_preview = None
self._gen = self._parse_results(stream)
@@ -202,7 +207,9 @@ class ResultsReader(object):
return self
def next(self):
return self._gen.next()
return next(self._gen)
__next__ = next
def _parse_results(self, stream):
"""Parse results and messages out of *stream*."""
@@ -233,7 +240,7 @@ class ResultsReader(object):
if event == 'start':
values = []
elif event == 'end':
field_name = elem.attrib['k'].encode('utf8')
field_name = elem.attrib['k']
if len(values) == 1:
result[field_name] = values[0]
else:
@@ -254,7 +261,7 @@ class ResultsReader(object):
def __itertext(self):
tag = self.tag
if not isinstance(tag, basestring) and tag is not None:
if not isinstance(tag, six.string_types) and tag is not None:
return
if self.text:
yield self.text
@@ -265,7 +272,7 @@ class ResultsReader(object):
yield e.tail
text = "".join(__itertext(elem))
values.append(text.encode('utf8'))
values.append(text)
elem.clear()
elif elem.tag == 'msg':
@@ -273,7 +280,7 @@ class ResultsReader(object):
msg_type = elem.attrib['type']
elif event == 'end':
text = elem.text if elem.text is not None else ""
yield Message(msg_type, text.encode('utf8'))
yield Message(msg_type, text)
elem.clear()
except SyntaxError as pe:
# This is here to handle the same incorrect return from
+2 -2
View File
@@ -30,7 +30,7 @@
field-name = ( "_" / alpha ) *( alpha / digit / "_" / "." / "-" )
It does not show that :code:`field-name` values may be comma-separated. This is because Splunk strips commas from
the command line. A search command will never see them.
the command line. A search command will never see them.
2. Search commands targeting versions of Splunk prior to 6.3 must be statically configured as follows:
@@ -134,7 +134,7 @@
.. topic:: References
1. `Search command style guide <http://docs.splunk.com/Documentation/Splunk/6.0/Search/Searchcommandstyleguide>`_
1. `Search command style guide <http://docs.splunk.com/Documentation/Splunk/6.0/Search/Searchcommandstyleguide>`__
2. `Commands.conf.spec <http://docs.splunk.com/Documentation/Splunk/5.0.5/Admin/Commandsconf>`_
+21 -18
View File
@@ -15,6 +15,7 @@
# under the License.
from __future__ import absolute_import, division, print_function, unicode_literals
from splunklib import six
try:
from collections import OrderedDict # must be python 2.7
@@ -22,7 +23,7 @@ except ImportError:
from ..ordereddict import OrderedDict
from inspect import getmembers, isclass, isfunction
from itertools import imap
from splunklib.six.moves import map as imap
from .internals import ConfigurationSettingsType, json_encode_string
from .validators import OptionName
@@ -35,7 +36,7 @@ class Configuration(object):
variable to search command classes that don't have one. The :code:`name` is derived from the name of the class.
By convention command class names end with the word "Command". To derive :code:`name` the word "Command" is removed
from the end of the class name and then converted to lower case for conformance with the `Search command style guide
<http://docs.splunk.com/Documentation/Splunk/latest/Search/Searchcommandstyleguide>`_
<http://docs.splunk.com/Documentation/Splunk/latest/Search/Searchcommandstyleguide>`__
"""
def __init__(self, o=None, **kwargs):
@@ -69,15 +70,15 @@ class Configuration(object):
# Set command name
name = o.__name__
if name.endswith(b'Command'):
name = name[:-len(b'Command')]
o.name = unicode(name.lower())
if name.endswith('Command'):
name = name[:-len('Command')]
o.name = six.text_type(name.lower())
# Construct ConfigurationSettings instance for the command class
o.ConfigurationSettings = ConfigurationSettingsType(
module=o.__module__ + b'.' + o.__name__,
name=b'ConfigurationSettings',
module=o.__module__ + '.' + o.__name__,
name='ConfigurationSettings',
bases=(o.ConfigurationSettings,))
ConfigurationSetting.fix_up(o.ConfigurationSettings, self.settings)
@@ -138,7 +139,7 @@ class ConfigurationSetting(property):
for name, setting in definitions:
if setting._name is None:
setting._name = name = unicode(name)
setting._name = name = six.text_type(name)
else:
name = setting._name
@@ -195,8 +196,8 @@ class ConfigurationSetting(property):
del values[name]
if len(values) > 0:
settings = sorted(list(values.iteritems()))
settings = imap(lambda (n, v): '{}={}'.format(n, repr(v)), settings)
settings = sorted(list(six.iteritems(values)))
settings = imap(lambda n_v: '{}={}'.format(n_v[0], repr(n_v[1])), settings)
raise AttributeError('Inapplicable configuration settings: ' + ', '.join(settings))
cls.configuration_setting_definitions = definitions
@@ -228,8 +229,9 @@ class Option(property):
Short form (recommended). When you are satisfied with built-in or custom validation behaviors.
.. code-block:: python
.. code-block:: python
:linenos:
from splunklib.searchcommands.decorators import Option
from splunklib.searchcommands.validators import Fieldname
@@ -246,8 +248,9 @@ class Option(property):
also provide a deleter. You must be prepared to accept a value of :const:`None` which indicates that your
:code:`Option` is unset.
.. code-block:: python
.. code-block:: python
:linenos:
from splunklib.searchcommands import Option
@Option()
@@ -357,7 +360,7 @@ class Option(property):
self._option = option
self._is_set = False
validator = self.validator
self._format = unicode if validator is None else validator.format
self._format = six.text_type if validator is None else validator.format
def __repr__(self):
return '(' + repr(self.name) + ', ' + repr(self._format(self.value)) + ')'
@@ -417,24 +420,24 @@ class Option(property):
def __init__(self, command):
definitions = type(command).option_definitions
item_class = Option.Item
OrderedDict.__init__(self, imap(lambda (name, option): (option.name, item_class(command, option)), definitions))
OrderedDict.__init__(self, ((option.name, item_class(command, option)) for (name, option) in definitions))
def __repr__(self):
text = 'Option.View([' + ','.join(imap(lambda item: repr(item), self.itervalues())) + '])'
text = 'Option.View([' + ','.join(imap(lambda item: repr(item), six.itervalues(self))) + '])'
return text
def __str__(self):
text = ' '.join([str(item) for item in self.itervalues() if item.is_set])
text = ' '.join([str(item) for item in six.itervalues(self) if item.is_set])
return text
# region Methods
def get_missing(self):
missing = [item.name for item in self.itervalues() if item.is_required and not item.is_set]
missing = [item.name for item in six.itervalues(self) if item.is_required and not item.is_set]
return missing if len(missing) > 0 else None
def reset(self):
for value in self.itervalues():
for value in six.itervalues(self):
value.reset()
pass
+4 -3
View File
@@ -18,7 +18,8 @@ from __future__ import absolute_import, division, print_function, unicode_litera
from logging import getLogger, root, StreamHandler
from logging.config import fileConfig
from os import chdir, environ, getcwdu, path
from os import chdir, environ, path
from splunklib.six.moves import getcwd
import sys
@@ -96,7 +97,7 @@ def configure_logging(logger_name, filename=None):
filename = path.realpath(filename)
if filename != _current_logging_configuration_file:
working_directory = getcwdu()
working_directory = getcwd()
chdir(app_root)
try:
fileConfig(filename, {'SPLUNK_HOME': splunk_home})
@@ -112,7 +113,7 @@ def configure_logging(logger_name, filename=None):
_current_logging_configuration_file = None
splunk_home = path.abspath(path.join(getcwdu(), environ.get('SPLUNK_HOME', '')))
splunk_home = path.abspath(path.join(getcwd(), environ.get('SPLUNK_HOME', '')))
app_file = getattr(sys.modules['__main__'], '__file__', sys.executable)
app_root = path.dirname(path.abspath(path.dirname(app_file)))
+11 -4
View File
@@ -16,7 +16,8 @@
from __future__ import absolute_import, division, print_function, unicode_literals
from itertools import imap
from splunklib import six
from splunklib.six.moves import map as imap
from .decorators import ConfigurationSetting
from .search_command import SearchCommand
@@ -113,10 +114,10 @@ class EventingCommand(SearchCommand):
''')
type = ConfigurationSetting(readonly=True, value='eventing', doc='''
type = ConfigurationSetting(readonly=True, value='events', doc='''
Command type
Fixed: :const:`'eventing'`.
Fixed: :const:`'events'`.
Supported by: SCP 2
@@ -135,8 +136,14 @@ class EventingCommand(SearchCommand):
raise AttributeError('No EventingCommand.transform override')
SearchCommand.ConfigurationSettings.fix_up(command)
# TODO: Stop looking like a dictionary because we don't obey the semantics
# N.B.: Does not use Python 2 dict copy semantics
def iteritems(self):
iteritems = SearchCommand.ConfigurationSettings.iteritems(self)
return imap(lambda (name, value): (name, 'events' if name == 'type' else value), iteritems)
return imap(lambda name_value: (name_value[0], 'events' if name_value[0] == 'type' else name_value[1]), iteritems)
# N.B.: Does not use Python 3 dict view semantics
if not six.PY2:
items = iteritems
# endregion
+8 -7
View File
@@ -20,6 +20,7 @@ from logging import getLogger
import os
import sys
import traceback
from splunklib import six
if sys.platform == 'win32':
from signal import signal, CTRL_BREAK_EVENT, SIGBREAK, SIGINT, SIGTERM
@@ -36,11 +37,11 @@ class ExternalSearchCommand(object):
"""
def __init__(self, path, argv=None, environ=None):
if not isinstance(path, (bytes, unicode)):
if not isinstance(path, (bytes, six.text_type)):
raise ValueError('Expected a string value for path, not {}'.format(repr(path)))
self._logger = getLogger(self.__class__.__name__)
self._path = unicode(path)
self._path = six.text_type(path)
self._argv = None
self._environ = None
@@ -89,7 +90,7 @@ class ExternalSearchCommand(object):
self._execute(self._path, self._argv, self._environ)
except:
error_type, error, tb = sys.exc_info()
message = 'Command execution failed: ' + unicode(error)
message = 'Command execution failed: ' + six.text_type(error)
self._logger.error(message + '\nTraceback:\n' + ''.join(traceback.format_tb(tb)))
sys.exit(1)
@@ -104,13 +105,13 @@ class ExternalSearchCommand(object):
:param argv: Argument list.
:type argv: list or tuple
The arguments to the child process should start with the name of the command being run, but this is not
enforced. A value of :const:`None` specifies that the base name of path name :param:`path` should be used.
The arguments to the child process should start with the name of the command being run, but this is not
enforced. A value of :const:`None` specifies that the base name of path name :param:`path` should be used.
:param environ: A mapping which is used to define the environment variables for the new process.
:type environ: dict or None.
This mapping is used instead of the current processs environment. A value of :const:`None` specifies that
the :data:`os.environ` mapping should be used.
This mapping is used instead of the current processs environment. A value of :const:`None` specifies that
the :data:`os.environ` mapping should be used.
:return: None
+52 -22
View File
@@ -19,7 +19,8 @@ from __future__ import absolute_import, division, print_function, unicode_litera
from .decorators import ConfigurationSetting
from .search_command import SearchCommand
from itertools import imap, ifilter
from splunklib import six
from splunklib.six.moves import map as imap, filter as ifilter
# P1 [O] TODO: Discuss generates_timeorder in the class-level documentation for GeneratingCommand
@@ -56,7 +57,7 @@ class GeneratingCommand(SearchCommand):
+==========+=====================================+============================================+
| streams | streaming=True[,local=[True|False]] | type='streaming'[,distributed=[true|false] |
+----------+-------------------------------------+--------------------------------------------+
| events | retainsevents=True, streaming=False | type='eventing' |
| events | retainsevents=True, streaming=False | type='events' |
+----------+-------------------------------------+--------------------------------------------+
| reports | streaming=False | type='reporting' |
+----------+-------------------------------------+--------------------------------------------+
@@ -92,9 +93,10 @@ class GeneratingCommand(SearchCommand):
+==========+===================================================+===================================================+
| streams | 1. Add this line to your command's stanza in | 1. Add this configuration setting to your code: |
| | | |
| | default/commands.conf. | .. code-block:: python |
| | .. code-block:: python | @Configuration(distributed=True) |
| | local = false | class SomeCommand(GeneratingCommand) |
| | default/commands.conf:: | .. code-block:: python |
| | | |
| | local = false | @Configuration(distributed=True) |
| | | class SomeCommand(GeneratingCommand) |
| | | ... |
| | 2. Restart splunk | |
| | | 2. You are good to go; no need to restart Splunk |
@@ -112,29 +114,33 @@ class GeneratingCommand(SearchCommand):
| | settings to your command class: | setting to your command class: |
| | | |
| | .. code-block:: python | .. code-block:: python |
| | @Configuration( | @Configuration(type='eventing') |
| | | |
| | @Configuration( | @Configuration(type='events') |
| | retainsevents=True, streaming=False) | class SomeCommand(GeneratingCommand) |
| | class SomeCommand(GeneratingCommand) | ... |
| | ... | |
| | | |
| | Or add these lines to default/commands.conf: | |
| | | |
| | .. code-block:: | |
| | retains events = true | |
| | .. code-block:: text | |
| | | |
| | retainsevents = true | |
| | streaming = false | |
+----------+---------------------------------------------------+---------------------------------------------------+
Configure your command class like this, if you wish to support both protocols:
.. code-block:: python
@Configuration(type='eventing', retainsevents=True, streaming=False)
.. code-block:: python
@Configuration(type='events', retainsevents=True, streaming=False)
class SomeCommand(GeneratingCommand)
...
You might also consider adding these lines to commands.conf instead of adding them to your command class:
.. code-block:: python
retains events = false
.. code-block:: python
retainsevents = false
streaming = false
Reporting Generating command
@@ -149,28 +155,32 @@ class GeneratingCommand(SearchCommand):
| | settings to your command class: | setting to your command class: |
| | | |
| | .. code-block:: python | .. code-block:: python |
| | | |
| | @Configuration(retainsevents=False) | @Configuration(type='reporting') |
| | class SomeCommand(GeneratingCommand) | class SomeCommand(GeneratingCommand) |
| | ... | ... |
| | | |
| | Or add this lines to default/commands.conf: | |
| | | |
| | .. code-block:: | |
| | retains events = false | |
| | .. code-block:: text | |
| | | |
| | retainsevents = false | |
| | streaming = false | |
+----------+---------------------------------------------------+---------------------------------------------------+
Configure your command class like this, if you wish to support both protocols:
.. code-block:: python
.. code-block:: python
@Configuration(type='reporting', streaming=False)
class SomeCommand(GeneratingCommand)
...
You might also consider adding these lines to commands.conf instead of adding them to your command class:
.. code-block:: python
retains events = false
.. code-block:: text
retainsevents = false
streaming = false
"""
@@ -193,9 +203,23 @@ class GeneratingCommand(SearchCommand):
:return: `None`.
"""
self._record_writer.write_records(self.generate())
if self._protocol_version == 2:
self._execute_v2(ifile, self.generate())
else:
assert self._protocol_version == 1
self._record_writer.write_records(self.generate())
self.finish()
def _execute_chunk_v2(self, process, chunk):
count = 0
for row in process:
self._record_writer.write_record(row)
count += 1
if count == self._record_writer._maxresultrows:
self._finished = False
return
self._finished = True
# endregion
# region Types
@@ -280,7 +304,7 @@ class GeneratingCommand(SearchCommand):
==================== ======================================================================================
Value Description
-------------------- --------------------------------------------------------------------------------------
:const:`'eventing'` Runs as the first command in the Splunk events pipeline. Cannot be distributed.
:const:`'events'` Runs as the first command in the Splunk events pipeline. Cannot be distributed.
:const:`'reporting'` Runs as the first command in the Splunk reports pipeline. Cannot be distributed.
:const:`'streaming'` Runs as the first command in the Splunk streams pipeline. May be distributed.
==================== ======================================================================================
@@ -303,16 +327,22 @@ class GeneratingCommand(SearchCommand):
if command.generate == GeneratingCommand.generate:
raise AttributeError('No GeneratingCommand.generate override')
# TODO: Stop looking like a dictionary because we don't obey the semantics
# N.B.: Does not use Python 2 dict copy semantics
def iteritems(self):
iteritems = SearchCommand.ConfigurationSettings.iteritems(self)
version = self.command.protocol_version
if version == 2:
iteritems = ifilter(lambda (name, value): name != 'distributed', iteritems)
if self.distributed and self.type == 'streaming':
iteritems = ifilter(lambda name_value1: name_value1[0] != 'distributed', iteritems)
if not self.distributed and self.type == 'streaming':
iteritems = imap(
lambda (name, value): (name, 'stateful') if name == 'type' else (name, value), iteritems)
lambda name_value: (name_value[0], 'stateful') if name_value[0] == 'type' else (name_value[0], name_value[1]), iteritems)
return iteritems
# N.B.: Does not use Python 3 dict view semantics
if not six.PY2:
items = iteritems
pass
# endregion
+165 -107
View File
@@ -14,45 +14,68 @@
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import, division, print_function, unicode_literals
from __future__ import absolute_import, division, print_function
from io import TextIOWrapper
from collections import deque, namedtuple
from splunklib import six
try:
from collections import OrderedDict # must be python 2.7
except ImportError:
from ..ordereddict import OrderedDict
from cStringIO import StringIO
from itertools import chain, imap
from splunklib.six.moves import StringIO
from itertools import chain
from splunklib.six.moves import map as imap
from json import JSONDecoder, JSONEncoder
from json.encoder import encode_basestring_ascii as json_encode_string
from urllib import unquote
from splunklib.six.moves import urllib
import csv
import gzip
import os
import re
import sys
import warnings
from . import environment
csv.field_size_limit(10485760) # The default value is 128KB; upping to 10MB. See SPL-12117 for background on this issue
if 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()
fileno = sys.stdout.fileno()
if implementation == 'PyPy':
sys.stdout = os.fdopen(fileno, 'wb', 0)
else:
from msvcrt import setmode
setmode(fileno, os.O_BINARY)
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
# 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'):
return fh.buffer
# check for python3
elif 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)
else:
import msvcrt
msvcrt.setmode(fh.fileno(), os.O_BINARY)
return fh
class CommandLineParser(object):
""" Parses the arguments to a search command.
r""" Parses the arguments to a search command.
A search command line is described by the following syntax.
@@ -210,7 +233,7 @@ class CommandLineParser(object):
_escaped_character_re = re.compile(r'(\\.|""|[\\"])')
_fieldnames_re = re.compile(r"""("(?:\\.|""|[^"])+"|(?:\\.|[^\s"])+)""")
_fieldnames_re = re.compile(r"""("(?:\\.|""|[^"\\])+"|(?:\\.|[^\s"])+)""")
_options_re = re.compile(r"""
# Captures a set of name/value pairs when used with re.finditer
@@ -243,7 +266,7 @@ class ConfigurationSettingsType(type):
"""
def __new__(mcs, module, name, bases):
mcs = super(ConfigurationSettingsType, mcs).__new__(mcs, name, bases, {})
mcs = super(ConfigurationSettingsType, mcs).__new__(mcs, str(name), bases, {})
return mcs
def __init__(cls, module, name, bases):
@@ -264,10 +287,10 @@ class ConfigurationSettingsType(type):
return value
specification = namedtuple(
b'ConfigurationSettingSpecification', (
b'type',
b'constraint',
b'supporting_protocols'))
'ConfigurationSettingSpecification', (
'type',
'constraint',
'supporting_protocols'))
# P1 [ ] TODO: Review ConfigurationSettingsType.specification_matrix for completeness and correctness
@@ -294,7 +317,7 @@ class ConfigurationSettingsType(type):
supporting_protocols=[1]),
'maxinputs': specification(
type=int,
constraint=lambda value: 0 <= value <= sys.maxint,
constraint=lambda value: 0 <= value <= six.MAXSIZE,
supporting_protocols=[2]),
'overrides_timeorder': specification(
type=bool,
@@ -321,22 +344,24 @@ class ConfigurationSettingsType(type):
constraint=None,
supporting_protocols=[1]),
'streaming_preop': specification(
type=(bytes, unicode),
type=(bytes, six.text_type),
constraint=None,
supporting_protocols=[1, 2]),
'type': specification(
type=(bytes, unicode),
constraint=lambda value: value in ('eventing', 'reporting', 'streaming'),
type=(bytes, six.text_type),
constraint=lambda value: value in ('events', 'reporting', 'streaming'),
supporting_protocols=[2])}
class CsvDialect(csv.Dialect):
""" Describes the properties of Splunk CSV streams """
delimiter = b','
quotechar = b'"'
delimiter = ','
quotechar = '"'
doublequote = True
skipinitialspace = False
lineterminator = b'\r\n'
lineterminator = '\r\n'
if sys.version_info >= (3, 0) and sys.platform == 'win32':
lineterminator = '\n'
quoting = csv.QUOTE_MINIMAL
@@ -344,8 +369,9 @@ class InputHeader(dict):
""" Represents a Splunk input header as a collection of name/value pairs.
"""
def __str__(self):
return '\n'.join([name + ':' + value for name, value in self.iteritems()])
return '\n'.join([name + ':' + value for name, value in six.iteritems(self)])
def read(self, ifile):
""" Reads an input header from an input file.
@@ -366,15 +392,16 @@ class InputHeader(dict):
# start of a new item
if name is not None:
self[name] = value[:-1] # value sans trailing newline
name, value = item[0], unquote(item[1])
name, value = item[0], urllib.parse.unquote(item[1])
elif name is not None:
# continuation of the current item
value += unquote(line)
value += urllib.parse.unquote(line)
if name is not None: self[name] = value[:-1] if value[-1] == '\n' else value
if name is not None:
self[name] = value[:-1] if value[-1] == '\n' else value
Message = namedtuple(b'Message', (b'type', b'text'))
Message = namedtuple('Message', ('type', 'text'))
class MetadataDecoder(JSONDecoder):
@@ -392,7 +419,7 @@ class MetadataDecoder(JSONDecoder):
while len(stack):
instance, member_name, dictionary = stack.popleft()
for name, value in dictionary.iteritems():
for name, value in six.iteritems(dictionary):
if isinstance(value, dict):
stack.append((dictionary, name, value))
@@ -468,7 +495,7 @@ class RecordWriter(object):
def __init__(self, ofile, maxresultrows=None):
self._maxresultrows = 50000 if maxresultrows is None else maxresultrows
self._ofile = ofile
self._ofile = set_binary_mode(ofile)
self._fieldnames = None
self._buffer = StringIO()
@@ -479,8 +506,8 @@ class RecordWriter(object):
self._inspector = OrderedDict()
self._chunk_count = 0
self._record_count = 0
self._total_record_count = 0L
self._pending_record_count = 0
self._committed_record_count = 0
@property
def is_flushed(self):
@@ -496,7 +523,37 @@ class RecordWriter(object):
@ofile.setter
def ofile(self, value):
self._ofile = value
self._ofile = set_binary_mode(value)
@property
def pending_record_count(self):
return self._pending_record_count
@property
def _record_count(self):
warnings.warn(
"_record_count will be deprecated soon. Use pending_record_count instead.",
PendingDeprecationWarning
)
return self.pending_record_count
@property
def committed_record_count(self):
return self._committed_record_count
@property
def _total_record_count(self):
warnings.warn(
"_total_record_count will be deprecated soon. Use committed_record_count instead.",
PendingDeprecationWarning
)
return self.committed_record_count
def write(self, data):
bytes_type = bytes if sys.version_info >= (3, 0) else str
if not isinstance(data, bytes_type):
data = data.encode('utf-8')
self.ofile.write(data)
def flush(self, finished=None, partial=None):
assert finished is None or isinstance(finished, bool)
@@ -520,11 +577,10 @@ class RecordWriter(object):
write_record(record)
def _clear(self):
self._buffer.reset()
self._buffer.seek(0)
self._buffer.truncate()
self._inspector.clear()
self._record_count = 0
self._flushed = False
self._pending_record_count = 0
def _ensure_validity(self):
if self._finished is True:
@@ -536,9 +592,8 @@ class RecordWriter(object):
fieldnames = self._fieldnames
if fieldnames is None:
self._fieldnames = fieldnames = record.keys()
value_list = imap(lambda fn: unicode(fn).encode('utf-8'), fieldnames)
value_list = imap(lambda fn: (fn, b'__mv_' + fn), value_list)
self._fieldnames = fieldnames = list(record.keys())
value_list = imap(lambda fn: (str(fn), str('__mv_') + str(fn)), fieldnames)
self._writerow(list(chain.from_iterable(value_list)))
get_value = record.get
@@ -561,14 +616,14 @@ class RecordWriter(object):
if len(value) > 1:
value_list = value
sv = b''
mv = b'$'
sv = ''
mv = '$'
for value in value_list:
if value is None:
sv += b'\n'
mv += b'$;$'
sv += '\n'
mv += '$;$'
continue
value_t = type(value)
@@ -577,17 +632,17 @@ class RecordWriter(object):
if value_t is bool:
value = str(value.real)
elif value_t is unicode:
value = value.encode('utf-8', errors='backslashreplace')
elif value_t is int or value_t is long or value_t is float or value_t is complex:
elif value_t is six.text_type:
value = value
elif isinstance(value, six.integer_types) or value_t is float or value_t is complex:
value = str(value)
elif issubclass(value_t, (dict, list, tuple)):
value = str(''.join(RecordWriter._iterencode_json(value, 0)))
else:
value = repr(value).encode('utf-8', errors='backslashreplace')
sv += value + b'\n'
mv += value.replace(b'$', b'$$') + b'$;$'
sv += value + '\n'
mv += value.replace('$', '$$') + '$;$'
values += (sv[:-1], mv[:-2])
continue
@@ -603,11 +658,13 @@ class RecordWriter(object):
values += (value, None)
continue
if value_t is unicode:
values += (value.encode('utf-8', errors='backslashreplace'), None)
if value_t is six.text_type:
if six.PY2:
value = value.encode('utf-8')
values += (value, None)
continue
if value_t is int or value_t is long or value_t is float or value_t is complex:
if isinstance(value, six.integer_types) or value_t is float or value_t is complex:
values += (str(value), None)
continue
@@ -615,12 +672,12 @@ class RecordWriter(object):
values += (str(''.join(RecordWriter._iterencode_json(value, 0))), None)
continue
values += (repr(value).encode('utf-8', errors='backslashreplace'), None)
values += (repr(value), None)
self._writerow(values)
self._record_count += 1
self._pending_record_count += 1
if self._record_count >= self._maxresultrows:
if self.pending_record_count >= self._maxresultrows:
self.flush(partial=True)
try:
@@ -657,10 +714,9 @@ class RecordWriterV1(RecordWriter):
RecordWriter.flush(self, finished, partial) # validates arguments and the state of this instance
if self._record_count > 0 or (self._chunk_count == 0 and 'messages' in self._inspector):
if self.pending_record_count > 0 or (self._chunk_count == 0 and 'messages' in self._inspector):
messages = self._inspector.get('messages')
write = self._ofile.write
if self._chunk_count == 0:
@@ -672,12 +728,12 @@ class RecordWriterV1(RecordWriter):
message_level = RecordWriterV1._message_level.get
for level, text in messages:
write(message_level(level, level))
write('=')
write(text)
write('\r\n')
self.write(message_level(level, level))
self.write('=')
self.write(text)
self.write('\r\n')
write('\r\n')
self.write('\r\n')
elif messages is not None:
@@ -695,10 +751,10 @@ class RecordWriterV1(RecordWriter):
for level, text in messages:
print(level, text, file=stderr)
write(self._buffer.getvalue())
self._clear()
self.write(self._buffer.getvalue())
self._chunk_count += 1
self._total_record_count += self._record_count
self._committed_record_count += self.pending_record_count
self._clear()
self._finished = finished is True
@@ -716,44 +772,43 @@ class RecordWriterV2(RecordWriter):
def flush(self, finished=None, partial=None):
RecordWriter.flush(self, finished, partial) # validates arguments and the state of this instance
if partial or not finished:
# Don't flush partial chunks, since the SCP v2 protocol does not
# provide a way to send partial chunks yet.
return
if not self.is_flushed:
self.write_chunk(finished=True)
def write_chunk(self, finished=None):
inspector = self._inspector
self._committed_record_count += self.pending_record_count
self._chunk_count += 1
if self._flushed is False:
# TODO: DVPL-6448: splunklib.searchcommands | Add support for partial: true when it is implemented in
# ChunkedExternProcessor (See SPL-103525)
#
# We will need to replace the following block of code with this block:
#
# metadata = [item for item in (('inspector', inspector), ('finished', finished), ('partial', partial))]
#
# if partial is True:
# finished = False
self._total_record_count += self._record_count
self._chunk_count += 1
if len(inspector) == 0:
inspector = None
# TODO: DVPL-6448: splunklib.searchcommands | Add support for partial: true when it is implemented in
# ChunkedExternProcessor (See SPL-103525)
#
# We will need to replace the following block of code with this block:
#
# metadata = [
# ('inspector', self._inspector if len(self._inspector) else None),
# ('finished', finished),
# ('partial', partial)]
if len(inspector) == 0:
inspector = None
if partial is True:
finished = False
metadata = [item for item in ('inspector', inspector), ('finished', finished)]
self._write_chunk(metadata, self._buffer.getvalue())
self._clear()
elif finished is True:
self._write_chunk((('finished', True),), '')
self._finished = finished is True
metadata = [item for item in (('inspector', inspector), ('finished', finished))]
self._write_chunk(metadata, self._buffer.getvalue())
self._clear()
def write_metadata(self, configuration):
self._ensure_validity()
metadata = chain(configuration.iteritems(), (('inspector', self._inspector if self._inspector else None),))
metadata = chain(six.iteritems(configuration), (('inspector', self._inspector if self._inspector else None),))
self._write_chunk(metadata, '')
self._ofile.write('\n')
self.write('\n')
self._clear()
def write_metric(self, name, value):
@@ -761,26 +816,29 @@ class RecordWriterV2(RecordWriter):
self._inspector['metric.' + name] = value
def _clear(self):
RecordWriter._clear(self)
super(RecordWriterV2, self)._clear()
self._fieldnames = None
def _write_chunk(self, metadata, body):
if metadata:
metadata = str(''.join(self._iterencode_json(dict([(n, v) for n, v in metadata if v is not None]), 0)))
if sys.version_info >= (3, 0):
metadata = metadata.encode('utf-8')
metadata_length = len(metadata)
else:
metadata_length = 0
if sys.version_info >= (3, 0):
body = body.encode('utf-8')
body_length = len(body)
if not (metadata_length > 0 or body_length > 0):
return
start_line = b'chunked 1.0,' + bytes(metadata_length) + b',' + bytes(body_length) + b'\n'
write = self._ofile.write
write(start_line)
write(metadata)
write(body)
start_line = 'chunked 1.0,%s,%s\n' % (metadata_length, body_length)
self.write(start_line)
self.write(metadata)
self.write(body)
self._ofile.flush()
self._flushed = False
self._flushed = True
+4 -3
View File
@@ -23,6 +23,7 @@ from .decorators import ConfigurationSetting, Option
from .streaming_command import StreamingCommand
from .search_command import SearchCommand
from .validators import Set
from splunklib import six
class ReportingCommand(SearchCommand):
@@ -93,7 +94,7 @@ class ReportingCommand(SearchCommand):
self._configuration.streaming_preop = ' '.join(streaming_preop)
return
raise RuntimeError('Unrecognized reporting command phase: {}'.format(json_encode_string(unicode(phase))))
raise RuntimeError('Unrecognized reporting command phase: {}'.format(json_encode_string(six.text_type(phase))))
def reduce(self, records):
""" Override this method to produce a reporting data structure.
@@ -252,7 +253,7 @@ class ReportingCommand(SearchCommand):
cls._requires_preop = False
return
f = vars(command)[b'map'] # Function backing the map method
f = vars(command)['map'] # Function backing the map method
# EXPLANATION OF PREVIOUS STATEMENT: There is no way to add custom attributes to methods. See [Why does
# setattr fail on a method](http://stackoverflow.com/questions/7891277/why-does-setattr-fail-on-a-bound-method) for a discussion of this issue.
@@ -265,7 +266,7 @@ class ReportingCommand(SearchCommand):
# Create new StreamingCommand.ConfigurationSettings class
module = command.__module__ + b'.' + command.__name__ + b'.map'
module = command.__module__ + '.' + command.__name__ + '.map'
name = b'ConfigurationSettings'
bases = (StreamingCommand.ConfigurationSettings,)
+97 -71
View File
@@ -19,22 +19,30 @@ from __future__ import absolute_import, division, print_function, unicode_litera
# Absolute imports
from collections import namedtuple
import io
try:
from collections import OrderedDict # must be python 2.7
except ImportError:
from ..ordereddict import OrderedDict
from copy import deepcopy
from cStringIO import StringIO
from itertools import chain, ifilter, imap, islice, izip
from logging import _levelNames, getLevelName, getLogger
from splunklib.six.moves import StringIO
from itertools import chain, islice
from splunklib.six.moves import filter as ifilter, map as imap, zip as izip
from splunklib import six
if six.PY2:
from logging import _levelNames, getLevelName, getLogger
else:
from logging import _nameToLevel as _levelNames, getLevelName, getLogger
try:
from shutil import make_archive
except ImportError:
# Used for recording, skip on python 2.6
pass
from time import time
from urllib import unquote
from urlparse import urlsplit
from splunklib.six.moves.urllib.parse import unquote
from splunklib.six.moves.urllib.parse import urlsplit
from warnings import warn
from xml.etree import ElementTree
@@ -47,7 +55,7 @@ import traceback
# Relative imports
from . internals import (
from .internals import (
CommandLineParser,
CsvDialect,
InputHeader,
@@ -63,6 +71,7 @@ from . internals import (
from . import Boolean, Option, environment
from ..client import Service
# ----------------------------------------------------------------------------------------------------------------------
# P1 [ ] TODO: Log these issues against ChunkedExternProcessor
@@ -89,6 +98,7 @@ class SearchCommand(object):
""" Represents a custom search command.
"""
def __init__(self):
# Variables that may be used, but not altered by derived classes
@@ -150,7 +160,7 @@ class SearchCommand(object):
def logging_level(self, value):
if value is None:
value = self._default_logging_level
if isinstance(value, (bytes, unicode)):
if isinstance(value, (bytes, six.text_type)):
try:
level = _levelNames[value.upper()]
except KeyError:
@@ -246,7 +256,7 @@ class SearchCommand(object):
invocation.
:return: Search results info:const:`None`, if the search results info file associated with the command
invocation is inaccessible.
invocation is inaccessible.
:rtype: SearchResultsInfo or NoneType
"""
@@ -269,10 +279,10 @@ class SearchCommand(object):
path = os.path.join(dispatch_dir, 'info.csv')
try:
with open(path, 'rb') as f:
with io.open(path, 'r') as f:
reader = csv.reader(f, dialect=CsvDialect)
fields = reader.next()
values = reader.next()
fields = next(reader)
values = next(reader)
except IOError as error:
if error.errno == 2:
self.logger.error('Search results info file {} does not exist.'.format(json_encode_string(path)))
@@ -290,7 +300,7 @@ class SearchCommand(object):
except ValueError:
return value
info = ObjectView(dict(imap(lambda (f, v): (convert_field(f), convert_value(v)), izip(fields, values))))
info = ObjectView(dict(imap(lambda f_v: (convert_field(f_v[0]), convert_value(f_v[1])), izip(fields, values))))
try:
count_map = info.countMap
@@ -307,7 +317,7 @@ class SearchCommand(object):
except AttributeError:
pass
else:
messages = ifilter(lambda (t, m): t or m, izip(msg_type.split('\n'), msg_text.split('\n')))
messages = ifilter(lambda t_m: t_m[0] or t_m[1], izip(msg_type.split('\n'), msg_text.split('\n')))
info.msg = [Message(message) for message in messages]
del info.msgType
@@ -328,6 +338,7 @@ class SearchCommand(object):
specifying this pair of configuration settings in commands.conf:
.. code-block:: python
enableheader = true
requires_srinfo = true
@@ -335,8 +346,8 @@ class SearchCommand(object):
:code:`requires_srinfo` setting is false by default. Hence, you must set it.
:return: :class:`splunklib.client.Service`, if :code:`enableheader` and :code:`requires_srinfo` are both
:code:`true`. Otherwise, if either :code:`enableheader` or :code:`requires_srinfo` are :code:`false`, a value
of :code:`None` is returned.
:code:`true`. Otherwise, if either :code:`enableheader` or :code:`requires_srinfo` are :code:`false`, a value
of :code:`None` is returned.
"""
if self._service is not None:
@@ -387,7 +398,7 @@ class SearchCommand(object):
:return: :const:`None`
"""
self._record_writer.flush(partial=True)
self._record_writer.flush(finished=False)
def prepare(self):
""" Prepare for execution.
@@ -443,7 +454,7 @@ class SearchCommand(object):
def _map(metadata_map):
metadata = {}
for name, value in metadata_map.iteritems():
for name, value in six.iteritems(metadata_map):
if isinstance(value, dict):
value = _map(value)
else:
@@ -493,7 +504,7 @@ class SearchCommand(object):
'username':
(lambda v: v.ppc_user, lambda s: s.search_results_info)}}
_MetadataSource = namedtuple(b'Source', (b'argv', b'input_header', b'search_results_info'))
_MetadataSource = namedtuple('Source', ('argv', 'input_header', 'search_results_info'))
def _prepare_protocol_v1(self, argv, ifile, ofile):
@@ -580,7 +591,7 @@ class SearchCommand(object):
ifile = self._prepare_protocol_v1(argv, ifile, ofile)
self._record_writer.write_record(dict(
(n, ','.join(v) if isinstance(v, (list, tuple)) else v) for n, v in self._configuration.iteritems()))
(n, ','.join(v) if isinstance(v, (list, tuple)) else v) for n, v in six.iteritems(self._configuration)))
self.finish()
elif argv[1] == '__EXECUTE__':
@@ -608,7 +619,7 @@ class SearchCommand(object):
raise RuntimeError(message)
except (SyntaxError, ValueError) as error:
self.write_error(unicode(error))
self.write_error(six.text_type(error))
self.flush()
exit(0)
@@ -645,7 +656,7 @@ class SearchCommand(object):
# noinspection PyBroadException
try:
debug('Reading metadata')
metadata, body = self._read_chunk(ifile)
metadata, body = self._read_chunk(self._as_binary_stream(ifile))
action = getattr(metadata, 'action', None)
@@ -695,9 +706,10 @@ class SearchCommand(object):
for arg in args:
result = arg.split('=', 1)
if len(result) == 1:
self.fieldnames.append(result[0])
self.fieldnames.append(str(result[0]))
else:
name, value = result
name = str(name)
try:
option = self.options[name]
except KeyError:
@@ -723,7 +735,7 @@ class SearchCommand(object):
if error_count > 0:
exit(1)
debug(' command: %s', unicode(self))
debug(' command: %s', six.text_type(self))
debug('Preparing for execution')
self.prepare()
@@ -741,7 +753,7 @@ class SearchCommand(object):
setattr(info, attr, [arg for arg in getattr(info, attr) if not arg.startswith('record=')])
metadata = MetadataEncoder().encode(self._metadata)
ifile.record('chunked 1.0,', unicode(len(metadata)), ',0\n', metadata)
ifile.record('chunked 1.0,', six.text_type(len(metadata)), ',0\n', metadata)
if self.show_configuration:
self.write_info(self.name + ' command configuration: ' + str(self._configuration))
@@ -764,7 +776,6 @@ class SearchCommand(object):
# noinspection PyBroadException
try:
debug('Executing under protocol_version=2')
self._records = self._records_protocol_v2
self._metadata.action = 'execute'
self._execute(ifile, None)
except SystemExit:
@@ -798,15 +809,15 @@ class SearchCommand(object):
:param name: Name of the metric.
:type name: basestring
:param value: A 4-tuple containing the value of metric :param:`name` where
:param value: A 4-tuple containing the value of metric ``name`` where
value[0] = Elapsed seconds or :const:`None`.
value[1] = Number of invocations or :const:`None`.
value[2] = Input count or :const:`None`.
value[3] = Output count or :const:`None`.
The :data:`SearchMetric` type provides a convenient encapsulation of :param:`value`.
The :data:`SearchMetric` type provides a convenient encapsulation of :param:`value`.
The :data:`SearchMetric` type provides a convenient encapsulation of ``value``.
The :data:`SearchMetric` type provides a convenient encapsulation of ``value``.
:return: :const:`None`.
@@ -821,6 +832,8 @@ class SearchCommand(object):
_encoded_value = re.compile(r'\$(?P<item>(?:\$\$|[^$])*)\$(?:;|$)') # matches a single value in an encoded list
# Note: Subclasses must override this method so that it can be called
# called as self._execute(ifile, None)
def _execute(self, ifile, process):
""" Default processing loop
@@ -834,22 +847,38 @@ class SearchCommand(object):
:rtype: NoneType
"""
self._record_writer.write_records(process(self._records(ifile)))
self.finish()
if self.protocol_version == 1:
self._record_writer.write_records(process(self._records(ifile)))
self.finish()
else:
assert self._protocol_version == 2
self._execute_v2(ifile, process)
@staticmethod
def _read_chunk(ifile):
def _as_binary_stream(ifile):
naught = ifile.read(0)
if isinstance(naught, bytes):
return ifile
# noinspection PyBroadException
try:
header = ifile.readline()
return ifile.buffer
except AttributeError as error:
raise RuntimeError('Failed to get underlying buffer: {}'.format(error))
@staticmethod
def _read_chunk(istream):
# noinspection PyBroadException
assert isinstance(istream.read(0), six.binary_type), 'Stream must be binary'
try:
header = istream.readline()
except Exception as error:
raise RuntimeError('Failed to read transport header: {}'.format(error))
if not header:
return None
match = SearchCommand._header.match(header)
match = SearchCommand._header.match(six.ensure_str(header))
if match is None:
raise RuntimeError('Failed to parse transport header: {}'.format(header))
@@ -859,35 +888,39 @@ class SearchCommand(object):
body_length = int(body_length)
try:
metadata = ifile.read(metadata_length)
metadata = istream.read(metadata_length)
except Exception as error:
raise RuntimeError('Failed to read metadata of length {}: {}'.format(metadata_length, error))
decoder = MetadataDecoder()
try:
metadata = decoder.decode(metadata)
metadata = decoder.decode(six.ensure_str(metadata))
except Exception as error:
raise RuntimeError('Failed to parse metadata of length {}: {}'.format(metadata_length, error))
# if body_length <= 0:
# return metadata, ''
body = ""
try:
body = ifile.read(body_length)
if body_length > 0:
body = istream.read(body_length)
except Exception as error:
raise RuntimeError('Failed to read body of length {}: {}'.format(body_length, error))
return metadata, body
return metadata, six.ensure_str(body)
_header = re.compile(r'chunked\s+1.0\s*,\s*(\d+)\s*,\s*(\d+)\s*\n')
def _records_protocol_v1(self, ifile):
return self._read_csv_records(ifile)
def _read_csv_records(self, ifile):
reader = csv.reader(ifile, dialect=CsvDialect)
try:
fieldnames = reader.next()
fieldnames = next(reader)
except StopIteration:
return
@@ -908,51 +941,36 @@ class SearchCommand(object):
record[fieldname] = value
yield record
def _records_protocol_v2(self, ifile):
def _execute_v2(self, ifile, process):
istream = self._as_binary_stream(ifile)
while True:
result = self._read_chunk(ifile)
result = self._read_chunk(istream)
if not result:
return
metadata, body = result
action = getattr(metadata, 'action', None)
if action != 'execute':
raise RuntimeError('Expected execute action, not {}'.format(action))
finished = getattr(metadata, 'finished', False)
self._finished = getattr(metadata, 'finished', False)
self._record_writer.is_flushed = False
if len(body) > 0:
reader = csv.reader(StringIO(body), dialect=CsvDialect)
self._execute_chunk_v2(process, result)
try:
fieldnames = reader.next()
except StopIteration:
return
self._record_writer.write_chunk(finished=self._finished)
mv_fieldnames = dict([(name, name[len('__mv_'):]) for name in fieldnames if name.startswith('__mv_')])
def _execute_chunk_v2(self, process, chunk):
metadata, body = chunk
if len(mv_fieldnames) == 0:
for values in reader:
yield OrderedDict(izip(fieldnames, values))
else:
for values in reader:
record = OrderedDict()
for fieldname, value in izip(fieldnames, values):
if fieldname.startswith('__mv_'):
if len(value) > 0:
record[mv_fieldnames[fieldname]] = self._decode_list(value)
elif fieldname not in record:
record[fieldname] = value
yield record
if finished:
if len(body) <= 0:
return
self.flush()
records = self._read_csv_records(StringIO(body))
self._record_writer.write_records(process(records))
def _report_unexpected_error(self):
@@ -1003,7 +1021,8 @@ class SearchCommand(object):
:return: String representation of this instance
"""
text = ', '.join(imap(lambda (name, value): name + '=' + json_encode_string(unicode(value)), self.iteritems()))
#text = ', '.join(imap(lambda (name, value): name + '=' + json_encode_string(unicode(value)), self.iteritems()))
text = ', '.join(['{}={}'.format(name, json_encode_string(six.text_type(value))) for (name, value) in six.iteritems(self)])
return text
# region Methods
@@ -1022,20 +1041,27 @@ class SearchCommand(object):
"""
return
# TODO: Stop looking like a dictionary because we don't obey the semantics
# N.B.: Does not use Python 2 dict copy semantics
def iteritems(self):
definitions = type(self).configuration_setting_definitions
version = self.command.protocol_version
return ifilter(
lambda (name, value): value is not None, imap(
lambda name_value1: name_value1[1] is not None, imap(
lambda setting: (setting.name, setting.__get__(self)), ifilter(
lambda setting: setting.is_supported_by_protocol(version), definitions)))
# N.B.: Does not use Python 3 dict view semantics
if not six.PY2:
items = iteritems
pass # endregion
pass # endregion
SearchMetric = namedtuple(b'SearchMetric', (b'elapsed_seconds', b'invocation_count', b'input_count', b'output_count'))
SearchMetric = namedtuple('SearchMetric', ('elapsed_seconds', 'invocation_count', 'input_count', 'output_count'))
def dispatch(command_class, argv=sys.argv, input_file=sys.stdin, output_file=sys.stdout, module_name=None):
@@ -1065,7 +1091,7 @@ def dispatch(command_class, argv=sys.argv, input_file=sys.stdin, output_file=sys
**Example**
.. code-block:: python
.. code-block:: python
:linenos:
#!/usr/bin/env python
@@ -1081,7 +1107,7 @@ def dispatch(command_class, argv=sys.argv, input_file=sys.stdin, output_file=sys
**Example**
.. code-block:: python
.. code-block:: python
:linenos:
from splunklib.searchcommands import dispatch, StreamingCommand, Configuration, Option, validators
+12 -5
View File
@@ -16,7 +16,8 @@
from __future__ import absolute_import, division, print_function, unicode_literals
from itertools import ifilter, imap
from splunklib import six
from splunklib.six.moves import map as imap, filter as ifilter
from .decorators import ConfigurationSetting
from .search_command import SearchCommand
@@ -172,17 +173,23 @@ class StreamingCommand(SearchCommand):
raise AttributeError('No StreamingCommand.stream override')
return
# TODO: Stop looking like a dictionary because we don't obey the semantics
# N.B.: Does not use Python 2 dict copy semantics
def iteritems(self):
iteritems = SearchCommand.ConfigurationSettings.iteritems(self)
version = self.command.protocol_version
if version == 1:
if self.required_fields is None:
iteritems = ifilter(lambda (name, value): name != 'clear_required_fields', iteritems)
iteritems = ifilter(lambda name_value: name_value[0] != 'clear_required_fields', iteritems)
else:
iteritems = ifilter(lambda (name, value): name != 'distributed', iteritems)
if self.distributed:
iteritems = ifilter(lambda name_value2: name_value2[0] != 'distributed', iteritems)
if not self.distributed:
iteritems = imap(
lambda (name, value): (name, 'stateful') if name == 'type' else (name, value), iteritems)
lambda name_value1: (name_value1[0], 'stateful') if name_value1[0] == 'type' else (name_value1[0], name_value1[1]), iteritems)
return iteritems
# N.B.: Does not use Python 3 dict view semantics
if not six.PY2:
items = iteritems
# endregion
+37 -27
View File
@@ -18,11 +18,13 @@ from __future__ import absolute_import, division, print_function, unicode_litera
from json.encoder import encode_basestring_ascii as json_encode_string
from collections import namedtuple
from cStringIO import StringIO
from splunklib.six.moves import StringIO
from io import open
import csv
import os
import re
from splunklib import six
from splunklib.six.moves import getcwd
class Validator(object):
@@ -58,7 +60,7 @@ class Boolean(Validator):
def __call__(self, value):
if not (value is None or isinstance(value, bool)):
value = unicode(value).lower()
value = six.text_type(value).lower()
if value not in Boolean.truth_values:
raise ValueError('Unrecognized truth value: {0}'.format(value))
value = Boolean.truth_values[value]
@@ -79,9 +81,9 @@ class Code(Validator):
def __init__(self, mode='eval'):
"""
:param mode: Specifies what kind of code must be compiled; it can be :const:`'exec'`, if source consists of a
sequence of statements, :const:`'eval'`, if it consists of a single expression, or :const:`'single'` if it
consists of a single interactive statement. In the latter case, expression statements that evaluate to
something other than :const:`None` will be printed.
sequence of statements, :const:`'eval'`, if it consists of a single expression, or :const:`'single'` if it
consists of a single interactive statement. In the latter case, expression statements that evaluate to
something other than :const:`None` will be printed.
:type mode: unicode or bytes
"""
@@ -91,14 +93,19 @@ class Code(Validator):
if value is None:
return None
try:
return Code.object(compile(value, 'string', self._mode), unicode(value))
return Code.object(compile(value, 'string', self._mode), six.text_type(value))
except (SyntaxError, TypeError) as error:
raise ValueError(error.message)
if six.PY2:
message = error.message
else:
message = str(error)
six.raise_from(ValueError(message), error)
def format(self, value):
return None if value is None else value.source
object = namedtuple(b'Code', (b'object', 'source'))
object = namedtuple('Code', ('object', 'source'))
class Fieldname(Validator):
@@ -109,7 +116,7 @@ class Fieldname(Validator):
def __call__(self, value):
if value is not None:
value = unicode(value)
value = six.text_type(value)
if Fieldname.pattern.match(value) is None:
raise ValueError('Illegal characters in fieldname: {}'.format(value))
return value
@@ -132,7 +139,7 @@ class File(Validator):
if value is None:
return value
path = unicode(value)
path = six.text_type(value)
if not os.path.isabs(path):
path = os.path.join(self.directory, path)
@@ -149,7 +156,7 @@ class File(Validator):
return None if value is None else value.name
_var_run_splunk = os.path.join(
os.environ['SPLUNK_HOME'] if 'SPLUNK_HOME' in os.environ else os.getcwdu(), 'var', 'run', 'splunk')
os.environ['SPLUNK_HOME'] if 'SPLUNK_HOME' in os.environ else getcwd(), 'var', 'run', 'splunk')
class Integer(Validator):
@@ -183,7 +190,10 @@ class Integer(Validator):
if value is None:
return None
try:
value = long(value)
if six.PY2:
value = long(value)
else:
value = int(value)
except ValueError:
raise ValueError('Expected integer value, not {}'.format(json_encode_string(value)))
@@ -191,7 +201,7 @@ class Integer(Validator):
return value
def format(self, value):
return None if value is None else unicode(long(value))
return None if value is None else six.text_type(int(value))
class Duration(Validator):
@@ -244,10 +254,10 @@ class List(Validator):
class Dialect(csv.Dialect):
""" Describes the properties of list option values. """
strict = True
delimiter = b','
quotechar = b'"'
delimiter = str(',')
quotechar = str('"')
doublequote = True
lineterminator = b'\n'
lineterminator = str('\n')
skipinitialspace = True
quoting = csv.QUOTE_MINIMAL
@@ -262,7 +272,7 @@ class List(Validator):
return value
try:
value = csv.reader([value], self.Dialect).next()
value = next(csv.reader([value], self.Dialect))
except csv.Error as error:
raise ValueError(error)
@@ -297,7 +307,7 @@ class Map(Validator):
if value is None:
return None
value = unicode(value)
value = six.text_type(value)
if value not in self.membership:
raise ValueError('Unrecognized value: {0}'.format(value))
@@ -305,7 +315,7 @@ class Map(Validator):
return self.membership[value]
def format(self, value):
return None if value is None else self.membership.keys()[self.membership.values().index(value)]
return None if value is None else list(self.membership.keys())[list(self.membership.values()).index(value)]
class Match(Validator):
@@ -313,19 +323,19 @@ class Match(Validator):
"""
def __init__(self, name, pattern, flags=0):
self.name = unicode(name)
self.name = six.text_type(name)
self.pattern = re.compile(pattern, flags)
def __call__(self, value):
if value is None:
return None
value = unicode(value)
value = six.text_type(value)
if self.pattern.match(value) is None:
raise ValueError('Expected {}, not {}'.format(self.name, json_encode_string(value)))
return value
def format(self, value):
return None if value is None else unicode(value)
return None if value is None else six.text_type(value)
class OptionName(Validator):
@@ -336,13 +346,13 @@ class OptionName(Validator):
def __call__(self, value):
if value is not None:
value = unicode(value)
value = six.text_type(value)
if OptionName.pattern.match(value) is None:
raise ValueError('Illegal characters in option name: {}'.format(value))
return value
def format(self, value):
return None if value is None else unicode(value)
return None if value is None else six.text_type(value)
class RegularExpression(Validator):
@@ -353,9 +363,9 @@ class RegularExpression(Validator):
if value is None:
return None
try:
value = re.compile(unicode(value))
value = re.compile(six.text_type(value))
except re.error as error:
raise ValueError('{}: {}'.format(unicode(error).capitalize(), value))
raise ValueError('{}: {}'.format(six.text_type(error).capitalize(), value))
return value
def format(self, value):
@@ -372,7 +382,7 @@ class Set(Validator):
def __call__(self, value):
if value is None:
return None
value = unicode(value)
value = six.text_type(value)
if value not in self.membership:
raise ValueError('Unrecognized value: {}'.format(value))
return value
+980
View File
@@ -0,0 +1,980 @@
# Copyright (c) 2010-2020 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""Utilities for writing code that runs on Python 2 and 3"""
from __future__ import absolute_import
import functools
import itertools
import operator
import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.14.0"
# Useful for very coarse version differentiation.
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
PY34 = sys.version_info[0:2] >= (3, 4)
if PY3:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
if sys.platform.startswith("java"):
# Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object):
def __len__(self):
return 1 << 31
try:
len(X())
except OverflowError:
# 32-bit
MAXSIZE = int((1 << 31) - 1)
else:
# 64-bit
MAXSIZE = int((1 << 63) - 1)
del X
def _add_doc(func, doc):
"""Add documentation to a function."""
func.__doc__ = doc
def _import_module(name):
"""Import module, returning the module after the last dot."""
__import__(name)
return sys.modules[name]
class _LazyDescr(object):
def __init__(self, name):
self.name = name
def __get__(self, obj, tp):
result = self._resolve()
setattr(obj, self.name, result) # Invokes __set__.
try:
# This is a bit ugly, but it avoids running this again by
# removing this descriptor.
delattr(obj.__class__, self.name)
except AttributeError:
pass
return result
class MovedModule(_LazyDescr):
def __init__(self, name, old, new=None):
super(MovedModule, self).__init__(name)
if PY3:
if new is None:
new = name
self.mod = new
else:
self.mod = old
def _resolve(self):
return _import_module(self.mod)
def __getattr__(self, attr):
_module = self._resolve()
value = getattr(_module, attr)
setattr(self, attr, value)
return value
class _LazyModule(types.ModuleType):
def __init__(self, name):
super(_LazyModule, self).__init__(name)
self.__doc__ = self.__class__.__doc__
def __dir__(self):
attrs = ["__doc__", "__name__"]
attrs += [attr.name for attr in self._moved_attributes]
return attrs
# Subclasses should override this
_moved_attributes = []
class MovedAttribute(_LazyDescr):
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
super(MovedAttribute, self).__init__(name)
if PY3:
if new_mod is None:
new_mod = name
self.mod = new_mod
if new_attr is None:
if old_attr is None:
new_attr = name
else:
new_attr = old_attr
self.attr = new_attr
else:
self.mod = old_mod
if old_attr is None:
old_attr = name
self.attr = old_attr
def _resolve(self):
module = _import_module(self.mod)
return getattr(module, self.attr)
class _SixMetaPathImporter(object):
"""
A meta path importer to import six.moves and its submodules.
This class implements a PEP302 finder and loader. It should be compatible
with Python 2.5 and all existing versions of Python3
"""
def __init__(self, six_module_name):
self.name = six_module_name
self.known_modules = {}
def _add_module(self, mod, *fullnames):
for fullname in fullnames:
self.known_modules[self.name + "." + fullname] = mod
def _get_module(self, fullname):
return self.known_modules[self.name + "." + fullname]
def find_module(self, fullname, path=None):
if fullname in self.known_modules:
return self
return None
def __get_module(self, fullname):
try:
return self.known_modules[fullname]
except KeyError:
raise ImportError("This loader does not know module " + fullname)
def load_module(self, fullname):
try:
# in case of a reload
return sys.modules[fullname]
except KeyError:
pass
mod = self.__get_module(fullname)
if isinstance(mod, MovedModule):
mod = mod._resolve()
else:
mod.__loader__ = self
sys.modules[fullname] = mod
return mod
def is_package(self, fullname):
"""
Return true, if the named module is a package.
We need this method to get correct spec objects with
Python 3.4 (see PEP451)
"""
return hasattr(self.__get_module(fullname), "__path__")
def get_code(self, fullname):
"""Return None
Required, if is_package is implemented"""
self.__get_module(fullname) # eventually raises ImportError
return None
get_source = get_code # same as get_code
_importer = _SixMetaPathImporter(__name__)
class _MovedItems(_LazyModule):
"""Lazy loading of moved objects"""
__path__ = [] # mark as package
_moved_attributes = [
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
MovedAttribute("intern", "__builtin__", "sys"),
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
MovedAttribute("getoutput", "commands", "subprocess"),
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
MovedAttribute("reduce", "__builtin__", "functools"),
MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
MovedAttribute("StringIO", "StringIO", "io"),
MovedAttribute("UserDict", "UserDict", "collections"),
MovedAttribute("UserList", "UserList", "collections"),
MovedAttribute("UserString", "UserString", "collections"),
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"),
MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"),
MovedModule("copyreg", "copy_reg"),
MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"),
MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"),
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
MovedModule("http_cookies", "Cookie", "http.cookies"),
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
MovedModule("html_parser", "HTMLParser", "html.parser"),
MovedModule("http_client", "httplib", "http.client"),
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"),
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
MovedModule("cPickle", "cPickle", "pickle"),
MovedModule("queue", "Queue"),
MovedModule("reprlib", "repr"),
MovedModule("socketserver", "SocketServer"),
MovedModule("_thread", "thread", "_thread"),
MovedModule("tkinter", "Tkinter"),
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
MovedModule("tkinter_colorchooser", "tkColorChooser",
"tkinter.colorchooser"),
MovedModule("tkinter_commondialog", "tkCommonDialog",
"tkinter.commondialog"),
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
"tkinter.simpledialog"),
MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
]
# Add windows specific modules.
if sys.platform == "win32":
_moved_attributes += [
MovedModule("winreg", "_winreg"),
]
for attr in _moved_attributes:
setattr(_MovedItems, attr.name, attr)
if isinstance(attr, MovedModule):
_importer._add_module(attr, "moves." + attr.name)
del attr
_MovedItems._moved_attributes = _moved_attributes
moves = _MovedItems(__name__ + ".moves")
_importer._add_module(moves, "moves")
class Module_six_moves_urllib_parse(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_parse"""
_urllib_parse_moved_attributes = [
MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
MovedAttribute("urljoin", "urlparse", "urllib.parse"),
MovedAttribute("urlparse", "urlparse", "urllib.parse"),
MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
MovedAttribute("quote", "urllib", "urllib.parse"),
MovedAttribute("quote_plus", "urllib", "urllib.parse"),
MovedAttribute("unquote", "urllib", "urllib.parse"),
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"),
MovedAttribute("urlencode", "urllib", "urllib.parse"),
MovedAttribute("splitquery", "urllib", "urllib.parse"),
MovedAttribute("splittag", "urllib", "urllib.parse"),
MovedAttribute("splituser", "urllib", "urllib.parse"),
MovedAttribute("splitvalue", "urllib", "urllib.parse"),
MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
MovedAttribute("uses_params", "urlparse", "urllib.parse"),
MovedAttribute("uses_query", "urlparse", "urllib.parse"),
MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
]
for attr in _urllib_parse_moved_attributes:
setattr(Module_six_moves_urllib_parse, attr.name, attr)
del attr
Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
"moves.urllib_parse", "moves.urllib.parse")
class Module_six_moves_urllib_error(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_error"""
_urllib_error_moved_attributes = [
MovedAttribute("URLError", "urllib2", "urllib.error"),
MovedAttribute("HTTPError", "urllib2", "urllib.error"),
MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
]
for attr in _urllib_error_moved_attributes:
setattr(Module_six_moves_urllib_error, attr.name, attr)
del attr
Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
"moves.urllib_error", "moves.urllib.error")
class Module_six_moves_urllib_request(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_request"""
_urllib_request_moved_attributes = [
MovedAttribute("urlopen", "urllib2", "urllib.request"),
MovedAttribute("install_opener", "urllib2", "urllib.request"),
MovedAttribute("build_opener", "urllib2", "urllib.request"),
MovedAttribute("pathname2url", "urllib", "urllib.request"),
MovedAttribute("url2pathname", "urllib", "urllib.request"),
MovedAttribute("getproxies", "urllib", "urllib.request"),
MovedAttribute("Request", "urllib2", "urllib.request"),
MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
MovedAttribute("FileHandler", "urllib2", "urllib.request"),
MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
MovedAttribute("urlretrieve", "urllib", "urllib.request"),
MovedAttribute("urlcleanup", "urllib", "urllib.request"),
MovedAttribute("URLopener", "urllib", "urllib.request"),
MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
MovedAttribute("parse_http_list", "urllib2", "urllib.request"),
MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"),
]
for attr in _urllib_request_moved_attributes:
setattr(Module_six_moves_urllib_request, attr.name, attr)
del attr
Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
"moves.urllib_request", "moves.urllib.request")
class Module_six_moves_urllib_response(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_response"""
_urllib_response_moved_attributes = [
MovedAttribute("addbase", "urllib", "urllib.response"),
MovedAttribute("addclosehook", "urllib", "urllib.response"),
MovedAttribute("addinfo", "urllib", "urllib.response"),
MovedAttribute("addinfourl", "urllib", "urllib.response"),
]
for attr in _urllib_response_moved_attributes:
setattr(Module_six_moves_urllib_response, attr.name, attr)
del attr
Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
"moves.urllib_response", "moves.urllib.response")
class Module_six_moves_urllib_robotparser(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_robotparser"""
_urllib_robotparser_moved_attributes = [
MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
]
for attr in _urllib_robotparser_moved_attributes:
setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
del attr
Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
"moves.urllib_robotparser", "moves.urllib.robotparser")
class Module_six_moves_urllib(types.ModuleType):
"""Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
__path__ = [] # mark as package
parse = _importer._get_module("moves.urllib_parse")
error = _importer._get_module("moves.urllib_error")
request = _importer._get_module("moves.urllib_request")
response = _importer._get_module("moves.urllib_response")
robotparser = _importer._get_module("moves.urllib_robotparser")
def __dir__(self):
return ['parse', 'error', 'request', 'response', 'robotparser']
_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
"moves.urllib")
def add_move(move):
"""Add an item to six.moves."""
setattr(_MovedItems, move.name, move)
def remove_move(name):
"""Remove item from six.moves."""
try:
delattr(_MovedItems, name)
except AttributeError:
try:
del moves.__dict__[name]
except KeyError:
raise AttributeError("no such move, %r" % (name,))
if PY3:
_meth_func = "__func__"
_meth_self = "__self__"
_func_closure = "__closure__"
_func_code = "__code__"
_func_defaults = "__defaults__"
_func_globals = "__globals__"
else:
_meth_func = "im_func"
_meth_self = "im_self"
_func_closure = "func_closure"
_func_code = "func_code"
_func_defaults = "func_defaults"
_func_globals = "func_globals"
try:
advance_iterator = next
except NameError:
def advance_iterator(it):
return it.next()
next = advance_iterator
try:
callable = callable
except NameError:
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
if PY3:
def get_unbound_function(unbound):
return unbound
create_bound_method = types.MethodType
def create_unbound_method(func, cls):
return func
Iterator = object
else:
def get_unbound_function(unbound):
return unbound.im_func
def create_bound_method(func, obj):
return types.MethodType(func, obj, obj.__class__)
def create_unbound_method(func, cls):
return types.MethodType(func, None, cls)
class Iterator(object):
def next(self):
return type(self).__next__(self)
callable = callable
_add_doc(get_unbound_function,
"""Get the function out of a possibly unbound function""")
get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_closure = operator.attrgetter(_func_closure)
get_function_code = operator.attrgetter(_func_code)
get_function_defaults = operator.attrgetter(_func_defaults)
get_function_globals = operator.attrgetter(_func_globals)
if PY3:
def iterkeys(d, **kw):
return iter(d.keys(**kw))
def itervalues(d, **kw):
return iter(d.values(**kw))
def iteritems(d, **kw):
return iter(d.items(**kw))
def iterlists(d, **kw):
return iter(d.lists(**kw))
viewkeys = operator.methodcaller("keys")
viewvalues = operator.methodcaller("values")
viewitems = operator.methodcaller("items")
else:
def iterkeys(d, **kw):
return d.iterkeys(**kw)
def itervalues(d, **kw):
return d.itervalues(**kw)
def iteritems(d, **kw):
return d.iteritems(**kw)
def iterlists(d, **kw):
return d.iterlists(**kw)
viewkeys = operator.methodcaller("viewkeys")
viewvalues = operator.methodcaller("viewvalues")
viewitems = operator.methodcaller("viewitems")
_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
_add_doc(iteritems,
"Return an iterator over the (key, value) pairs of a dictionary.")
_add_doc(iterlists,
"Return an iterator over the (key, [values]) pairs of a dictionary.")
if PY3:
def b(s):
return s.encode("latin-1")
def u(s):
return s
unichr = chr
import struct
int2byte = struct.Struct(">B").pack
del struct
byte2int = operator.itemgetter(0)
indexbytes = operator.getitem
iterbytes = iter
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
del io
_assertCountEqual = "assertCountEqual"
if sys.version_info[1] <= 1:
_assertRaisesRegex = "assertRaisesRegexp"
_assertRegex = "assertRegexpMatches"
_assertNotRegex = "assertNotRegexpMatches"
else:
_assertRaisesRegex = "assertRaisesRegex"
_assertRegex = "assertRegex"
_assertNotRegex = "assertNotRegex"
else:
def b(s):
return s
# Workaround for standalone backslash
def u(s):
return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
unichr = unichr
int2byte = chr
def byte2int(bs):
return ord(bs[0])
def indexbytes(buf, i):
return ord(buf[i])
iterbytes = functools.partial(itertools.imap, ord)
import StringIO
StringIO = BytesIO = StringIO.StringIO
_assertCountEqual = "assertItemsEqual"
_assertRaisesRegex = "assertRaisesRegexp"
_assertRegex = "assertRegexpMatches"
_assertNotRegex = "assertNotRegexpMatches"
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")
def assertCountEqual(self, *args, **kwargs):
return getattr(self, _assertCountEqual)(*args, **kwargs)
def assertRaisesRegex(self, *args, **kwargs):
return getattr(self, _assertRaisesRegex)(*args, **kwargs)
def assertRegex(self, *args, **kwargs):
return getattr(self, _assertRegex)(*args, **kwargs)
def assertNotRegex(self, *args, **kwargs):
return getattr(self, _assertNotRegex)(*args, **kwargs)
if PY3:
exec_ = getattr(moves.builtins, "exec")
def reraise(tp, value, tb=None):
try:
if value is None:
value = tp()
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
finally:
value = None
tb = None
else:
def exec_(_code_, _globs_=None, _locs_=None):
"""Execute code in a namespace."""
if _globs_ is None:
frame = sys._getframe(1)
_globs_ = frame.f_globals
if _locs_ is None:
_locs_ = frame.f_locals
del frame
elif _locs_ is None:
_locs_ = _globs_
exec("""exec _code_ in _globs_, _locs_""")
exec_("""def reraise(tp, value, tb=None):
try:
raise tp, value, tb
finally:
tb = None
""")
if sys.version_info[:2] > (3,):
exec_("""def raise_from(value, from_value):
try:
raise value from from_value
finally:
value = None
""")
else:
def raise_from(value, from_value):
raise value
print_ = getattr(moves.builtins, "print", None)
if print_ is None:
def print_(*args, **kwargs):
"""The new-style print function for Python 2.4 and 2.5."""
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
# If the file has an encoding, encode unicode with it.
if (isinstance(fp, file) and
isinstance(data, unicode) and
fp.encoding is not None):
errors = getattr(fp, "errors", None)
if errors is None:
errors = "strict"
data = data.encode(fp.encoding, errors)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
if isinstance(sep, unicode):
want_unicode = True
elif not isinstance(sep, str):
raise TypeError("sep must be None or a string")
end = kwargs.pop("end", None)
if end is not None:
if isinstance(end, unicode):
want_unicode = True
elif not isinstance(end, str):
raise TypeError("end must be None or a string")
if kwargs:
raise TypeError("invalid keyword arguments to print()")
if not want_unicode:
for arg in args:
if isinstance(arg, unicode):
want_unicode = True
break
if want_unicode:
newline = unicode("\n")
space = unicode(" ")
else:
newline = "\n"
space = " "
if sep is None:
sep = space
if end is None:
end = newline
for i, arg in enumerate(args):
if i:
write(sep)
write(arg)
write(end)
if sys.version_info[:2] < (3, 3):
_print = print_
def print_(*args, **kwargs):
fp = kwargs.get("file", sys.stdout)
flush = kwargs.pop("flush", False)
_print(*args, **kwargs)
if flush and fp is not None:
fp.flush()
_add_doc(reraise, """Reraise an exception.""")
if sys.version_info[0:2] < (3, 4):
# This does exactly the same what the :func:`py3:functools.update_wrapper`
# function does on Python versions after 3.2. It sets the ``__wrapped__``
# attribute on ``wrapper`` object and it doesn't raise an error if any of
# the attributes mentioned in ``assigned`` and ``updated`` are missing on
# ``wrapped`` object.
def _update_wrapper(wrapper, wrapped,
assigned=functools.WRAPPER_ASSIGNMENTS,
updated=functools.WRAPPER_UPDATES):
for attr in assigned:
try:
value = getattr(wrapped, attr)
except AttributeError:
continue
else:
setattr(wrapper, attr, value)
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
wrapper.__wrapped__ = wrapped
return wrapper
_update_wrapper.__doc__ = functools.update_wrapper.__doc__
def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
updated=functools.WRAPPER_UPDATES):
return functools.partial(_update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
wraps.__doc__ = functools.wraps.__doc__
else:
wraps = functools.wraps
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
# This requires a bit of explanation: the basic idea is to make a dummy
# metaclass for one level of class instantiation that replaces itself with
# the actual metaclass.
class metaclass(type):
def __new__(cls, name, this_bases, d):
if sys.version_info[:2] >= (3, 7):
# This version introduced PEP 560 that requires a bit
# of extra care (we mimic what is done by __build_class__).
resolved_bases = types.resolve_bases(bases)
if resolved_bases is not bases:
d['__orig_bases__'] = bases
else:
resolved_bases = bases
return meta(name, resolved_bases, d)
@classmethod
def __prepare__(cls, name, this_bases):
return meta.__prepare__(name, bases)
return type.__new__(metaclass, 'temporary_class', (), {})
def add_metaclass(metaclass):
"""Class decorator for creating a class with a metaclass."""
def wrapper(cls):
orig_vars = cls.__dict__.copy()
slots = orig_vars.get('__slots__')
if slots is not None:
if isinstance(slots, str):
slots = [slots]
for slots_var in slots:
orig_vars.pop(slots_var)
orig_vars.pop('__dict__', None)
orig_vars.pop('__weakref__', None)
if hasattr(cls, '__qualname__'):
orig_vars['__qualname__'] = cls.__qualname__
return metaclass(cls.__name__, cls.__bases__, orig_vars)
return wrapper
def ensure_binary(s, encoding='utf-8', errors='strict'):
"""Coerce **s** to six.binary_type.
For Python 2:
- `unicode` -> encoded to `str`
- `str` -> `str`
For Python 3:
- `str` -> encoded to `bytes`
- `bytes` -> `bytes`
"""
if isinstance(s, text_type):
return s.encode(encoding, errors)
elif isinstance(s, binary_type):
return s
else:
raise TypeError("not expecting type '%s'" % type(s))
def ensure_str(s, encoding='utf-8', errors='strict'):
"""Coerce *s* to `str`.
For Python 2:
- `unicode` -> encoded to `str`
- `str` -> `str`
For Python 3:
- `str` -> `str`
- `bytes` -> decoded to `str`
"""
if not isinstance(s, (text_type, binary_type)):
raise TypeError("not expecting type '%s'" % type(s))
if PY2 and isinstance(s, text_type):
s = s.encode(encoding, errors)
elif PY3 and isinstance(s, binary_type):
s = s.decode(encoding, errors)
return s
def ensure_text(s, encoding='utf-8', errors='strict'):
"""Coerce *s* to six.text_type.
For Python 2:
- `unicode` -> `unicode`
- `str` -> `unicode`
For Python 3:
- `str` -> `str`
- `bytes` -> decoded to `str`
"""
if isinstance(s, binary_type):
return s.decode(encoding, errors)
elif isinstance(s, text_type):
return s
else:
raise TypeError("not expecting type '%s'" % type(s))
def python_2_unicode_compatible(klass):
"""
A class decorator that defines __unicode__ and __str__ methods under Python 2.
Under Python 3 it does nothing.
To support Python 2 and 3 with a single code base, define a __str__ method
returning text and apply this decorator to the class.
"""
if PY2:
if '__str__' not in klass.__dict__:
raise ValueError("@python_2_unicode_compatible cannot be applied "
"to %s because it doesn't define __str__()." %
klass.__name__)
klass.__unicode__ = klass.__str__
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
return klass
# Complete the moves implementation.
# This code is at the end of this module to speed up module loading.
# Turn this module into a package.
__path__ = [] # required for PEP 302 and PEP 451
__package__ = __name__ # see PEP 366 @ReservedAssignment
if globals().get("__spec__") is not None:
__spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
# Remove other six meta path importers, since they cause problems. This can
# happen if six is removed from sys.modules and then reloaded. (Setuptools does
# this for some reason.)
if sys.meta_path:
for i, importer in enumerate(sys.meta_path):
# Here's some real nastiness: Another "instance" of the six module might
# be floating around. Therefore, we can't use isinstance() to check for
# the six meta path importer, since the other six instance will have
# inserted an importer with different class.
if (type(importer).__name__ == "_SixMetaPathImporter" and
importer.name == __name__):
del sys.meta_path[i]
break
del i, importer
# Finally, add the importer to the meta path import hook.
sys.meta_path.append(_importer)