mirror of
https://github.com/splunk/security_content
synced 2026-06-08 17:32:49 +00:00
Branch was auto-updated.
This commit is contained in:
+3
-2
@@ -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__))
|
||||
|
||||
|
||||
+93
-51
@@ -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
|
||||
|
||||
+66
-47
@@ -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'))
|
||||
|
||||
+37
-29
@@ -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``
|
||||
"""
|
||||
|
||||
+4
-3
@@ -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.
|
||||
|
||||
+8
-1
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
+2
-1
@@ -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")
|
||||
|
||||
|
||||
+9
-8
@@ -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:
|
||||
|
||||
+3
-1
@@ -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
@@ -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.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
+22
-15
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 process’s environment. A value of :const:`None` specifies that
|
||||
the :data:`os.environ` mapping should be used.
|
||||
This mapping is used instead of the current process’s environment. A value of :const:`None` specifies that
|
||||
the :data:`os.environ` mapping should be used.
|
||||
|
||||
:return: None
|
||||
|
||||
|
||||
+52
-22
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Vendored
+980
@@ -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)
|
||||
Reference in New Issue
Block a user