This commit is contained in:
Nick Lang
2019-05-10 09:16:33 -06:00
committed by GitHub
parent 01e62965a1
commit 206f5e2754
34 changed files with 1300 additions and 826 deletions
+78 -68
View File
@@ -16,34 +16,34 @@ import sys, os
# If extensions (or modules to document with autodoc) are in another directory, # If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the # add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here. # documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.')) # sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ----------------------------------------------------- # -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here. # If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0' # needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions # Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest'] extensions = ["sphinx.ext.autodoc", "sphinx.ext.doctest"]
autoclass_content = "both" autoclass_content = "both"
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates'] templates_path = ["_templates"]
# The suffix of source filenames. # The suffix of source filenames.
source_suffix = '.rst' source_suffix = ".rst"
# The encoding of source files. # The encoding of source files.
#source_encoding = 'utf-8-sig' # source_encoding = 'utf-8-sig'
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = "index"
# General information about the project. # General information about the project.
project = u'Elasticsearch' project = u"Elasticsearch"
copyright = u'2013, Honza Král' copyright = u"2013, Honza Král"
# The version info for the project you're documenting, acts as replacement for # The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
@@ -51,6 +51,7 @@ copyright = u'2013, Honza Král'
# #
import elasticsearch import elasticsearch
# The short X.Y version. # The short X.Y version.
version = elasticsearch.__versionstr__ version = elasticsearch.__versionstr__
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
@@ -58,40 +59,40 @@ release = version
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
#language = None # language = None
# There are two options for replacing |today|: either, you set today to some # There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used: # non-false value, then it is used:
#today = '' # today = ''
# Else, today_fmt is used as the format for a strftime call. # Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y' # today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
exclude_patterns = ['_build'] exclude_patterns = ["_build"]
# The reST default role (used for this markup: `text`) to use for all documents. # The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None # default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text. # If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True # add_function_parentheses = True
# If true, the current module name will be prepended to all description # If true, the current module name will be prepended to all description
# unit titles (such as .. function::). # unit titles (such as .. function::).
#add_module_names = True # add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the # If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default. # output. They are ignored by default.
#show_authors = False # show_authors = False
# The name of the Pygments (syntax highlighting) style to use. # The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx' pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting. # A list of ignored prefixes for module index sorting.
#modindex_common_prefix = [] # modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents. # If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False # keep_warnings = False
# -- Options for HTML output --------------------------------------------------- # -- Options for HTML output ---------------------------------------------------
@@ -99,11 +100,12 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
on_rtd = os.environ.get('READTHEDOCS', None) == 'True' on_rtd = os.environ.get("READTHEDOCS", None) == "True"
if not on_rtd: # only import and set the theme if we're building docs locally if not on_rtd: # only import and set the theme if we're building docs locally
import sphinx_rtd_theme import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a theme # Theme options are theme-specific and customize the look and feel of a theme
@@ -113,116 +115,119 @@ if not on_rtd: # only import and set the theme if we're building docs locally
# Theme options are theme-specific and customize the look and feel of a theme # Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the # further. For a list of options available for each theme, see the
# documentation. # documentation.
#html_theme_options = {} # html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory. # Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = [] # html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to # The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation". # "<project> v<release> documentation".
#html_title = None # html_title = None
# A shorter title for the navigation bar. Default is the same as html_title. # A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None # html_short_title = None
# The name of an image file (relative to this directory) to place at the top # The name of an image file (relative to this directory) to place at the top
# of the sidebar. # of the sidebar.
#html_logo = None # html_logo = None
# The name of an image file (within the static path) to use as favicon of the # The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large. # pixels large.
#html_favicon = None # html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static'] html_static_path = ["_static"]
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format. # using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y' # html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to # If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities. # typographically correct entities.
#html_use_smartypants = True # html_use_smartypants = True
# Custom sidebar templates, maps document names to template names. # Custom sidebar templates, maps document names to template names.
#html_sidebars = {} # html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to # Additional templates that should be rendered to pages, maps page names to
# template names. # template names.
#html_additional_pages = {} # html_additional_pages = {}
# If false, no module index is generated. # If false, no module index is generated.
#html_domain_indices = True # html_domain_indices = True
# If false, no index is generated. # If false, no index is generated.
#html_use_index = True # html_use_index = True
# If true, the index is split into individual pages for each letter. # If true, the index is split into individual pages for each letter.
#html_split_index = False # html_split_index = False
# If true, links to the reST sources are added to the pages. # If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True # html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True # html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True # html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will # If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the # contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served. # base URL from which the finished HTML is served.
#html_use_opensearch = '' # html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml"). # This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None # html_file_suffix = None
# Output file base name for HTML help builder. # Output file base name for HTML help builder.
htmlhelp_basename = 'Elasticsearchdoc' htmlhelp_basename = "Elasticsearchdoc"
# -- Options for LaTeX output -------------------------------------------------- # -- Options for LaTeX output --------------------------------------------------
latex_elements = { latex_elements = {
# The paper size ('letterpaper' or 'a4paper'). # The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper', #'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt',
#'pointsize': '10pt', # Additional stuff for the LaTeX preamble.
#'preamble': '',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
} }
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]). # (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [ latex_documents = [
('index', 'Elasticsearch.tex', u'Elasticsearch Documentation', (
u'Honza Král', 'manual'), "index",
"Elasticsearch.tex",
u"Elasticsearch Documentation",
u"Honza Král",
"manual",
)
] ]
# The name of an image file (relative to this directory) to place at the top of # The name of an image file (relative to this directory) to place at the top of
# the title page. # the title page.
#latex_logo = None # latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts, # For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters. # not chapters.
#latex_use_parts = False # latex_use_parts = False
# If true, show page references after internal links. # If true, show page references after internal links.
#latex_show_pagerefs = False # latex_show_pagerefs = False
# If true, show URL addresses after external links. # If true, show URL addresses after external links.
#latex_show_urls = False # latex_show_urls = False
# Documents to append as an appendix to all manuals. # Documents to append as an appendix to all manuals.
#latex_appendices = [] # latex_appendices = []
# If false, no module index is generated. # If false, no module index is generated.
#latex_domain_indices = True # latex_domain_indices = True
# -- Options for manual page output -------------------------------------------- # -- Options for manual page output --------------------------------------------
@@ -230,12 +235,11 @@ latex_documents = [
# One entry per manual page. List of tuples # One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section). # (source start file, name, description, authors, manual section).
man_pages = [ man_pages = [
('index', 'elasticsearch-py', u'Elasticsearch Documentation', ("index", "elasticsearch-py", u"Elasticsearch Documentation", [u"Honza Král"], 1)
[u'Honza Král'], 1)
] ]
# If true, show URL addresses after external links. # If true, show URL addresses after external links.
#man_show_urls = False # man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------ # -- Options for Texinfo output ------------------------------------------------
@@ -244,19 +248,25 @@ man_pages = [
# (source start file, target name, title, author, # (source start file, target name, title, author,
# dir menu entry, description, category) # dir menu entry, description, category)
texinfo_documents = [ texinfo_documents = [
('index', 'Elasticsearch', u'Elasticsearch Documentation', (
u'Honza Král', 'Elasticsearch', 'One line description of project.', "index",
'Miscellaneous'), "Elasticsearch",
u"Elasticsearch Documentation",
u"Honza Král",
"Elasticsearch",
"One line description of project.",
"Miscellaneous",
)
] ]
# Documents to append as an appendix to all manuals. # Documents to append as an appendix to all manuals.
#texinfo_appendices = [] # texinfo_appendices = []
# If false, no module index is generated. # If false, no module index is generated.
#texinfo_domain_indices = True # texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'. # How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote' # texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu. # If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False # texinfo_no_detailmenu = False
+109 -68
View File
@@ -1,7 +1,8 @@
from .utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH from .utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH
class CatClient(NamespacedClient): class CatClient(NamespacedClient):
@query_params('format', 'h', 'help', 'local', 'master_timeout', 's', 'v') @query_params("format", "h", "help", "local", "master_timeout", "s", "v")
def aliases(self, name=None, params=None): def aliases(self, name=None, params=None):
""" """
@@ -19,11 +20,13 @@ class CatClient(NamespacedClient):
by by
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', _make_path('_cat', return self.transport.perform_request(
'aliases', name), params=params) "GET", _make_path("_cat", "aliases", name), params=params
)
@query_params('bytes', 'size', 'format', 'h', 'help', 'local', 'master_timeout', @query_params(
's', 'v') "bytes", "size", "format", "h", "help", "local", "master_timeout", "s", "v"
)
def allocation(self, node_id=None, params=None): def allocation(self, node_id=None, params=None):
""" """
Allocation provides a snapshot of how shards have located around the Allocation provides a snapshot of how shards have located around the
@@ -45,10 +48,11 @@ class CatClient(NamespacedClient):
by by
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', _make_path('_cat', return self.transport.perform_request(
'allocation', node_id), params=params) "GET", _make_path("_cat", "allocation", node_id), params=params
)
@query_params('size', 'format', 'h', 'help', 'local', 'master_timeout', 's', 'v') @query_params("size", "format", "h", "help", "local", "master_timeout", "s", "v")
def count(self, index=None, params=None): def count(self, index=None, params=None):
""" """
Count provides quick access to the document count of the entire cluster, Count provides quick access to the document count of the entire cluster,
@@ -68,11 +72,11 @@ class CatClient(NamespacedClient):
by by
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', _make_path('_cat', 'count', return self.transport.perform_request(
index), params=params) "GET", _make_path("_cat", "count", index), params=params
)
@query_params('format', 'h', 'help', 'local', 'master_timeout', 's', 'ts', @query_params("format", "h", "help", "local", "master_timeout", "s", "ts", "v")
'v')
def health(self, params=None): def health(self, params=None):
""" """
health is a terse, one-line representation of the same information from health is a terse, one-line representation of the same information from
@@ -91,10 +95,9 @@ class CatClient(NamespacedClient):
:arg ts: Set to false to disable timestamping, default True :arg ts: Set to false to disable timestamping, default True
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', '/_cat/health', return self.transport.perform_request("GET", "/_cat/health", params=params)
params=params)
@query_params('help', 's') @query_params("help", "s")
def help(self, params=None): def help(self, params=None):
""" """
A simple help for the cat api. A simple help for the cat api.
@@ -104,10 +107,22 @@ class CatClient(NamespacedClient):
:arg s: Comma-separated list of column names or column aliases to sort :arg s: Comma-separated list of column names or column aliases to sort
by by
""" """
return self.transport.perform_request('GET', '/_cat', params=params) return self.transport.perform_request("GET", "/_cat", params=params)
@query_params('bytes', 'time', 'size', 'format', 'h', 'health', 'help', 'local', @query_params(
'master_timeout', 'pri', 's', 'v') "bytes",
"time",
"size",
"format",
"h",
"health",
"help",
"local",
"master_timeout",
"pri",
"s",
"v",
)
def indices(self, index=None, params=None): def indices(self, index=None, params=None):
""" """
The indices command provides a cross-section of each index. The indices command provides a cross-section of each index.
@@ -133,10 +148,11 @@ class CatClient(NamespacedClient):
by by
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', _make_path('_cat', return self.transport.perform_request(
'indices', index), params=params) "GET", _make_path("_cat", "indices", index), params=params
)
@query_params('format', 'h', 'help', 'local', 'master_timeout', 's', 'v') @query_params("format", "h", "help", "local", "master_timeout", "s", "v")
def master(self, params=None): def master(self, params=None):
""" """
Displays the master's node ID, bound IP address, and node name. Displays the master's node ID, bound IP address, and node name.
@@ -153,11 +169,9 @@ class CatClient(NamespacedClient):
by by
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', '/_cat/master', return self.transport.perform_request("GET", "/_cat/master", params=params)
params=params)
@query_params('format', 'full_id', 'h', 'help', 'local', 'master_timeout', @query_params("format", "full_id", "h", "help", "local", "master_timeout", "s", "v")
's', 'v')
def nodes(self, params=None): def nodes(self, params=None):
""" """
The nodes command shows the cluster topology. The nodes command shows the cluster topology.
@@ -176,10 +190,11 @@ class CatClient(NamespacedClient):
by by
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', '/_cat/nodes', return self.transport.perform_request("GET", "/_cat/nodes", params=params)
params=params)
@query_params('bytes', 'time', 'size', 'format', 'h', 'help', 'master_timeout', 's', 'v') @query_params(
"bytes", "time", "size", "format", "h", "help", "master_timeout", "s", "v"
)
def recovery(self, index=None, params=None): def recovery(self, index=None, params=None):
""" """
recovery is a view of shard replication. recovery is a view of shard replication.
@@ -198,10 +213,22 @@ class CatClient(NamespacedClient):
by by
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', _make_path('_cat', return self.transport.perform_request(
'recovery', index), params=params) "GET", _make_path("_cat", "recovery", index), params=params
)
@query_params('bytes', 'time', 'size', 'format', 'h', 'help', 'local', 'master_timeout', 's', 'v') @query_params(
"bytes",
"time",
"size",
"format",
"h",
"help",
"local",
"master_timeout",
"s",
"v",
)
def shards(self, index=None, params=None): def shards(self, index=None, params=None):
""" """
The shards command is the detailed view of what nodes contain which shards. The shards command is the detailed view of what nodes contain which shards.
@@ -222,10 +249,11 @@ class CatClient(NamespacedClient):
by by
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', _make_path('_cat', return self.transport.perform_request(
'shards', index), params=params) "GET", _make_path("_cat", "shards", index), params=params
)
@query_params('bytes', 'size', 'format', 'h', 'help', 's', 'v') @query_params("bytes", "size", "format", "h", "help", "s", "v")
def segments(self, index=None, params=None): def segments(self, index=None, params=None):
""" """
The segments command is the detailed view of Lucene segments per index. The segments command is the detailed view of Lucene segments per index.
@@ -242,10 +270,11 @@ class CatClient(NamespacedClient):
by by
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', _make_path('_cat', return self.transport.perform_request(
'segments', index), params=params) "GET", _make_path("_cat", "segments", index), params=params
)
@query_params('format', 'h', 'help', 'local', 'master_timeout', 's', 'v') @query_params("format", "h", "help", "local", "master_timeout", "s", "v")
def pending_tasks(self, params=None): def pending_tasks(self, params=None):
""" """
pending_tasks provides the same information as the pending_tasks provides the same information as the
@@ -264,11 +293,11 @@ class CatClient(NamespacedClient):
by by
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', '/_cat/pending_tasks', return self.transport.perform_request(
params=params) "GET", "/_cat/pending_tasks", params=params
)
@query_params('format', 'h', 'help', 'local', 'master_timeout', 's', 'size', @query_params("format", "h", "help", "local", "master_timeout", "s", "size", "v")
'v')
def thread_pool(self, thread_pool_patterns=None, params=None): def thread_pool(self, thread_pool_patterns=None, params=None):
""" """
Get information about thread pools. Get information about thread pools.
@@ -289,11 +318,13 @@ class CatClient(NamespacedClient):
'', 'k', 'm', 'g', 't', 'p' '', 'k', 'm', 'g', 't', 'p'
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', _make_path('_cat', return self.transport.perform_request(
'thread_pool', thread_pool_patterns), params=params) "GET",
_make_path("_cat", "thread_pool", thread_pool_patterns),
params=params,
)
@query_params('bytes', 'format', 'h', 'help', 'local', 'master_timeout', @query_params("bytes", "format", "h", "help", "local", "master_timeout", "s", "v")
's', 'v')
def fielddata(self, fields=None, params=None): def fielddata(self, fields=None, params=None):
""" """
Shows information about currently loaded fielddata on a per-node basis. Shows information about currently loaded fielddata on a per-node basis.
@@ -314,10 +345,11 @@ class CatClient(NamespacedClient):
by by
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', _make_path('_cat', return self.transport.perform_request(
'fielddata', fields), params=params) "GET", _make_path("_cat", "fielddata", fields), params=params
)
@query_params('format', 'h', 'help', 'local', 'master_timeout', 's', 'v') @query_params("format", "h", "help", "local", "master_timeout", "s", "v")
def plugins(self, params=None): def plugins(self, params=None):
""" """
@@ -334,10 +366,9 @@ class CatClient(NamespacedClient):
by by
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', '/_cat/plugins', return self.transport.perform_request("GET", "/_cat/plugins", params=params)
params=params)
@query_params('format', 'h', 'help', 'local', 'master_timeout', 's', 'v') @query_params("format", "h", "help", "local", "master_timeout", "s", "v")
def nodeattrs(self, params=None): def nodeattrs(self, params=None):
""" """
@@ -354,10 +385,9 @@ class CatClient(NamespacedClient):
by by
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', '/_cat/nodeattrs', return self.transport.perform_request("GET", "/_cat/nodeattrs", params=params)
params=params)
@query_params('format', 'h', 'help', 'local', 'master_timeout', 's', 'v') @query_params("format", "h", "help", "local", "master_timeout", "s", "v")
def repositories(self, params=None): def repositories(self, params=None):
""" """
@@ -374,11 +404,13 @@ class CatClient(NamespacedClient):
by by
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', '/_cat/repositories', return self.transport.perform_request(
params=params) "GET", "/_cat/repositories", params=params
)
@query_params('format', 'h', 'help', 'ignore_unavailable', 'master_timeout', @query_params(
's', 'v') "format", "h", "help", "ignore_unavailable", "master_timeout", "s", "v"
)
def snapshots(self, repository, params=None): def snapshots(self, repository, params=None):
""" """
@@ -399,11 +431,21 @@ class CatClient(NamespacedClient):
""" """
if repository in SKIP_IN_PATH: if repository in SKIP_IN_PATH:
raise ValueError("Empty value passed for a required argument 'repository'.") raise ValueError("Empty value passed for a required argument 'repository'.")
return self.transport.perform_request('GET', _make_path('_cat', return self.transport.perform_request(
'snapshots', repository), params=params) "GET", _make_path("_cat", "snapshots", repository), params=params
)
@query_params('actions', 'detailed', 'format', 'h', 'help', 'nodes', @query_params(
'parent_task_id', 's', 'v') "actions",
"detailed",
"format",
"h",
"help",
"nodes",
"parent_task_id",
"s",
"v",
)
def tasks(self, params=None): def tasks(self, params=None):
""" """
@@ -425,10 +467,9 @@ class CatClient(NamespacedClient):
by by
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', '/_cat/tasks', return self.transport.perform_request("GET", "/_cat/tasks", params=params)
params=params)
@query_params('format', 'h', 'help', 'local', 'master_timeout', 's', 'v') @query_params("format", "h", "help", "local", "master_timeout", "s", "v")
def templates(self, name=None, params=None): def templates(self, name=None, params=None):
""" """
`<https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-templates.html>`_ `<https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-templates.html>`_
@@ -445,6 +486,6 @@ class CatClient(NamespacedClient):
by by
:arg v: Verbose mode. Display column headers, default False :arg v: Verbose mode. Display column headers, default False
""" """
return self.transport.perform_request('GET', _make_path('_cat', return self.transport.perform_request(
'templates', name), params=params) "GET", _make_path("_cat", "templates", name), params=params
)
+54 -33
View File
@@ -1,10 +1,19 @@
from .utils import NamespacedClient, query_params, _make_path from .utils import NamespacedClient, query_params, _make_path
class ClusterClient(NamespacedClient): class ClusterClient(NamespacedClient):
@query_params('level', 'local', 'master_timeout', 'timeout', @query_params(
'wait_for_active_shards', 'wait_for_events', "level",
'wait_for_no_relocating_shards', 'wait_for_nodes', "local",
'wait_for_status', 'wait_for_no_initializing_shards') "master_timeout",
"timeout",
"wait_for_active_shards",
"wait_for_events",
"wait_for_no_relocating_shards",
"wait_for_nodes",
"wait_for_status",
"wait_for_no_initializing_shards",
)
def health(self, index=None, params=None): def health(self, index=None, params=None):
""" """
Get a very simple status on the health of the cluster. Get a very simple status on the health of the cluster.
@@ -30,10 +39,11 @@ class ClusterClient(NamespacedClient):
:arg wait_for_status: Wait until cluster is in a specific state, default :arg wait_for_status: Wait until cluster is in a specific state, default
None, valid choices are: 'green', 'yellow', 'red' None, valid choices are: 'green', 'yellow', 'red'
""" """
return self.transport.perform_request('GET', _make_path('_cluster', return self.transport.perform_request(
'health', index), params=params) "GET", _make_path("_cluster", "health", index), params=params
)
@query_params('local', 'master_timeout') @query_params("local", "master_timeout")
def pending_tasks(self, params=None): def pending_tasks(self, params=None):
""" """
The pending cluster tasks API returns a list of any cluster-level The pending cluster tasks API returns a list of any cluster-level
@@ -45,11 +55,18 @@ class ClusterClient(NamespacedClient):
master node (default: false) master node (default: false)
:arg master_timeout: Specify timeout for connection to master :arg master_timeout: Specify timeout for connection to master
""" """
return self.transport.perform_request('GET', return self.transport.perform_request(
'/_cluster/pending_tasks', params=params) "GET", "/_cluster/pending_tasks", params=params
)
@query_params('allow_no_indices', 'expand_wildcards', 'flat_settings', @query_params(
'ignore_unavailable', 'local', 'master_timeout') "allow_no_indices",
"expand_wildcards",
"flat_settings",
"ignore_unavailable",
"local",
"master_timeout",
)
def state(self, metric=None, index=None, params=None): def state(self, metric=None, index=None, params=None):
""" """
Get a comprehensive state information of the whole cluster. Get a comprehensive state information of the whole cluster.
@@ -72,11 +89,12 @@ class ClusterClient(NamespacedClient):
:arg master_timeout: Specify timeout for connection to master :arg master_timeout: Specify timeout for connection to master
""" """
if index and not metric: if index and not metric:
metric = '_all' metric = "_all"
return self.transport.perform_request('GET', _make_path('_cluster', return self.transport.perform_request(
'state', metric, index), params=params) "GET", _make_path("_cluster", "state", metric, index), params=params
)
@query_params('flat_settings', 'timeout') @query_params("flat_settings", "timeout")
def stats(self, node_id=None, params=None): def stats(self, node_id=None, params=None):
""" """
The Cluster Stats API allows to retrieve statistics from a cluster wide The Cluster Stats API allows to retrieve statistics from a cluster wide
@@ -91,13 +109,14 @@ class ClusterClient(NamespacedClient):
:arg flat_settings: Return settings in flat format (default: false) :arg flat_settings: Return settings in flat format (default: false)
:arg timeout: Explicit operation timeout :arg timeout: Explicit operation timeout
""" """
url = '/_cluster/stats' url = "/_cluster/stats"
if node_id: if node_id:
url = _make_path('_cluster/stats/nodes', node_id) url = _make_path("_cluster/stats/nodes", node_id)
return self.transport.perform_request('GET', url, params=params) return self.transport.perform_request("GET", url, params=params)
@query_params('dry_run', 'explain', 'master_timeout', 'metric', @query_params(
'retry_failed', 'timeout') "dry_run", "explain", "master_timeout", "metric", "retry_failed", "timeout"
)
def reroute(self, body=None, params=None): def reroute(self, body=None, params=None):
""" """
Explicitly execute a cluster reroute allocation command including specific commands. Explicitly execute a cluster reroute allocation command including specific commands.
@@ -117,11 +136,11 @@ class ClusterClient(NamespacedClient):
too many subsequent allocation failures too many subsequent allocation failures
:arg timeout: Explicit operation timeout :arg timeout: Explicit operation timeout
""" """
return self.transport.perform_request('POST', '/_cluster/reroute', return self.transport.perform_request(
params=params, body=body) "POST", "/_cluster/reroute", params=params, body=body
)
@query_params('flat_settings', 'include_defaults', 'master_timeout', @query_params("flat_settings", "include_defaults", "master_timeout", "timeout")
'timeout')
def get_settings(self, params=None): def get_settings(self, params=None):
""" """
Get cluster settings. Get cluster settings.
@@ -134,10 +153,11 @@ class ClusterClient(NamespacedClient):
node node
:arg timeout: Explicit operation timeout :arg timeout: Explicit operation timeout
""" """
return self.transport.perform_request('GET', '/_cluster/settings', return self.transport.perform_request(
params=params) "GET", "/_cluster/settings", params=params
)
@query_params('flat_settings', 'master_timeout', 'timeout') @query_params("flat_settings", "master_timeout", "timeout")
def put_settings(self, body=None, params=None): def put_settings(self, body=None, params=None):
""" """
Update cluster wide specific settings. Update cluster wide specific settings.
@@ -150,10 +170,11 @@ class ClusterClient(NamespacedClient):
node node
:arg timeout: Explicit operation timeout :arg timeout: Explicit operation timeout
""" """
return self.transport.perform_request('PUT', '/_cluster/settings', return self.transport.perform_request(
params=params, body=body) "PUT", "/_cluster/settings", params=params, body=body
)
@query_params('include_disk_info', 'include_yes_decisions') @query_params("include_disk_info", "include_yes_decisions")
def allocation_explain(self, body=None, params=None): def allocation_explain(self, body=None, params=None):
""" """
`<http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-allocation-explain.html>`_ `<http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-allocation-explain.html>`_
@@ -165,6 +186,6 @@ class ClusterClient(NamespacedClient):
:arg include_yes_decisions: Return 'YES' decisions in explanation :arg include_yes_decisions: Return 'YES' decisions in explanation
(default: false) (default: false)
""" """
return self.transport.perform_request('GET', return self.transport.perform_request(
'/_cluster/allocation/explain', params=params, body=body) "GET", "/_cluster/allocation/explain", params=params, body=body
)
+4 -1
View File
@@ -79,7 +79,10 @@ class IndicesClient(NamespacedClient):
) )
@query_params( @query_params(
"master_timeout", "request_timeout", "wait_for_active_shards", "include_type_name" "master_timeout",
"request_timeout",
"wait_for_active_shards",
"include_type_name",
) )
def create(self, index, body=None, params=None): def create(self, index, body=None, params=None):
""" """
+20 -12
View File
@@ -1,7 +1,8 @@
from .utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH from .utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH
class IngestClient(NamespacedClient): class IngestClient(NamespacedClient):
@query_params('master_timeout') @query_params("master_timeout")
def get_pipeline(self, id=None, params=None): def get_pipeline(self, id=None, params=None):
""" """
`<https://www.elastic.co/guide/en/elasticsearch/plugins/current/ingest.html>`_ `<https://www.elastic.co/guide/en/elasticsearch/plugins/current/ingest.html>`_
@@ -10,10 +11,11 @@ class IngestClient(NamespacedClient):
:arg master_timeout: Explicit operation timeout for connection to master :arg master_timeout: Explicit operation timeout for connection to master
node node
""" """
return self.transport.perform_request('GET', _make_path('_ingest', return self.transport.perform_request(
'pipeline', id), params=params) "GET", _make_path("_ingest", "pipeline", id), params=params
)
@query_params('master_timeout', 'timeout') @query_params("master_timeout", "timeout")
def put_pipeline(self, id, body, params=None): def put_pipeline(self, id, body, params=None):
""" """
`<https://www.elastic.co/guide/en/elasticsearch/plugins/current/ingest.html>`_ `<https://www.elastic.co/guide/en/elasticsearch/plugins/current/ingest.html>`_
@@ -27,10 +29,11 @@ class IngestClient(NamespacedClient):
for param in (id, body): for param in (id, body):
if param in SKIP_IN_PATH: if param in SKIP_IN_PATH:
raise ValueError("Empty value passed for a required argument.") raise ValueError("Empty value passed for a required argument.")
return self.transport.perform_request('PUT', _make_path('_ingest', return self.transport.perform_request(
'pipeline', id), params=params, body=body) "PUT", _make_path("_ingest", "pipeline", id), params=params, body=body
)
@query_params('master_timeout', 'timeout') @query_params("master_timeout", "timeout")
def delete_pipeline(self, id, params=None): def delete_pipeline(self, id, params=None):
""" """
`<https://www.elastic.co/guide/en/elasticsearch/plugins/current/ingest.html>`_ `<https://www.elastic.co/guide/en/elasticsearch/plugins/current/ingest.html>`_
@@ -42,10 +45,11 @@ class IngestClient(NamespacedClient):
""" """
if id in SKIP_IN_PATH: if id in SKIP_IN_PATH:
raise ValueError("Empty value passed for a required argument 'id'.") raise ValueError("Empty value passed for a required argument 'id'.")
return self.transport.perform_request('DELETE', _make_path('_ingest', return self.transport.perform_request(
'pipeline', id), params=params) "DELETE", _make_path("_ingest", "pipeline", id), params=params
)
@query_params('verbose') @query_params("verbose")
def simulate(self, body, id=None, params=None): def simulate(self, body, id=None, params=None):
""" """
`<https://www.elastic.co/guide/en/elasticsearch/plugins/current/ingest.html>`_ `<https://www.elastic.co/guide/en/elasticsearch/plugins/current/ingest.html>`_
@@ -57,5 +61,9 @@ class IngestClient(NamespacedClient):
""" """
if body in SKIP_IN_PATH: if body in SKIP_IN_PATH:
raise ValueError("Empty value passed for a required argument 'body'.") raise ValueError("Empty value passed for a required argument 'body'.")
return self.transport.perform_request('GET', _make_path('_ingest', return self.transport.perform_request(
'pipeline', id, '_simulate'), params=params, body=body) "GET",
_make_path("_ingest", "pipeline", id, "_simulate"),
params=params,
body=body,
)
+2 -3
View File
@@ -1,11 +1,10 @@
from .utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH from .utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH
class RemoteClient(NamespacedClient): class RemoteClient(NamespacedClient):
@query_params() @query_params()
def info(self, params=None): def info(self, params=None):
""" """
`<http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-remote-info.html>`_ `<http://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-remote-info.html>`_
""" """
return self.transport.perform_request('GET', '/_remote/info', return self.transport.perform_request("GET", "/_remote/info", params=params)
params=params)
+45 -27
View File
@@ -1,7 +1,8 @@
from .utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH from .utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH
class SnapshotClient(NamespacedClient): class SnapshotClient(NamespacedClient):
@query_params('master_timeout', 'wait_for_completion') @query_params("master_timeout", "wait_for_completion")
def create(self, repository, snapshot, body=None, params=None): def create(self, repository, snapshot, body=None, params=None):
""" """
Create a snapshot in repository Create a snapshot in repository
@@ -18,10 +19,14 @@ class SnapshotClient(NamespacedClient):
for param in (repository, snapshot): for param in (repository, snapshot):
if param in SKIP_IN_PATH: if param in SKIP_IN_PATH:
raise ValueError("Empty value passed for a required argument.") raise ValueError("Empty value passed for a required argument.")
return self.transport.perform_request('PUT', _make_path('_snapshot', return self.transport.perform_request(
repository, snapshot), params=params, body=body) "PUT",
_make_path("_snapshot", repository, snapshot),
params=params,
body=body,
)
@query_params('master_timeout') @query_params("master_timeout")
def delete(self, repository, snapshot, params=None): def delete(self, repository, snapshot, params=None):
""" """
Deletes a snapshot from a repository. Deletes a snapshot from a repository.
@@ -35,10 +40,11 @@ class SnapshotClient(NamespacedClient):
for param in (repository, snapshot): for param in (repository, snapshot):
if param in SKIP_IN_PATH: if param in SKIP_IN_PATH:
raise ValueError("Empty value passed for a required argument.") raise ValueError("Empty value passed for a required argument.")
return self.transport.perform_request('DELETE', return self.transport.perform_request(
_make_path('_snapshot', repository, snapshot), params=params) "DELETE", _make_path("_snapshot", repository, snapshot), params=params
)
@query_params('ignore_unavailable', 'master_timeout', 'verbose') @query_params("ignore_unavailable", "master_timeout", "verbose")
def get(self, repository, snapshot, params=None): def get(self, repository, snapshot, params=None):
""" """
Retrieve information about a snapshot. Retrieve information about a snapshot.
@@ -56,10 +62,11 @@ class SnapshotClient(NamespacedClient):
for param in (repository, snapshot): for param in (repository, snapshot):
if param in SKIP_IN_PATH: if param in SKIP_IN_PATH:
raise ValueError("Empty value passed for a required argument.") raise ValueError("Empty value passed for a required argument.")
return self.transport.perform_request('GET', _make_path('_snapshot', return self.transport.perform_request(
repository, snapshot), params=params) "GET", _make_path("_snapshot", repository, snapshot), params=params
)
@query_params('master_timeout', 'timeout') @query_params("master_timeout", "timeout")
def delete_repository(self, repository, params=None): def delete_repository(self, repository, params=None):
""" """
Removes a shared file system repository. Removes a shared file system repository.
@@ -72,10 +79,11 @@ class SnapshotClient(NamespacedClient):
""" """
if repository in SKIP_IN_PATH: if repository in SKIP_IN_PATH:
raise ValueError("Empty value passed for a required argument 'repository'.") raise ValueError("Empty value passed for a required argument 'repository'.")
return self.transport.perform_request('DELETE', return self.transport.perform_request(
_make_path('_snapshot', repository), params=params) "DELETE", _make_path("_snapshot", repository), params=params
)
@query_params('local', 'master_timeout') @query_params("local", "master_timeout")
def get_repository(self, repository=None, params=None): def get_repository(self, repository=None, params=None):
""" """
Return information about registered repositories. Return information about registered repositories.
@@ -87,10 +95,11 @@ class SnapshotClient(NamespacedClient):
:arg master_timeout: Explicit operation timeout for connection to master :arg master_timeout: Explicit operation timeout for connection to master
node node
""" """
return self.transport.perform_request('GET', _make_path('_snapshot', return self.transport.perform_request(
repository), params=params) "GET", _make_path("_snapshot", repository), params=params
)
@query_params('master_timeout', 'timeout', 'verify') @query_params("master_timeout", "timeout", "verify")
def create_repository(self, repository, body, params=None): def create_repository(self, repository, body, params=None):
""" """
Registers a shared file system repository. Registers a shared file system repository.
@@ -106,10 +115,11 @@ class SnapshotClient(NamespacedClient):
for param in (repository, body): for param in (repository, body):
if param in SKIP_IN_PATH: if param in SKIP_IN_PATH:
raise ValueError("Empty value passed for a required argument.") raise ValueError("Empty value passed for a required argument.")
return self.transport.perform_request('PUT', _make_path('_snapshot', return self.transport.perform_request(
repository), params=params, body=body) "PUT", _make_path("_snapshot", repository), params=params, body=body
)
@query_params('master_timeout', 'wait_for_completion') @query_params("master_timeout", "wait_for_completion")
def restore(self, repository, snapshot, body=None, params=None): def restore(self, repository, snapshot, body=None, params=None):
""" """
Restore a snapshot. Restore a snapshot.
@@ -126,10 +136,14 @@ class SnapshotClient(NamespacedClient):
for param in (repository, snapshot): for param in (repository, snapshot):
if param in SKIP_IN_PATH: if param in SKIP_IN_PATH:
raise ValueError("Empty value passed for a required argument.") raise ValueError("Empty value passed for a required argument.")
return self.transport.perform_request('POST', _make_path('_snapshot', return self.transport.perform_request(
repository, snapshot, '_restore'), params=params, body=body) "POST",
_make_path("_snapshot", repository, snapshot, "_restore"),
params=params,
body=body,
)
@query_params('ignore_unavailable', 'master_timeout') @query_params("ignore_unavailable", "master_timeout")
def status(self, repository=None, snapshot=None, params=None): def status(self, repository=None, snapshot=None, params=None):
""" """
Return information about all currently running snapshots. By specifying Return information about all currently running snapshots. By specifying
@@ -144,10 +158,13 @@ class SnapshotClient(NamespacedClient):
:arg master_timeout: Explicit operation timeout for connection to master :arg master_timeout: Explicit operation timeout for connection to master
node node
""" """
return self.transport.perform_request('GET', _make_path('_snapshot', return self.transport.perform_request(
repository, snapshot, '_status'), params=params) "GET",
_make_path("_snapshot", repository, snapshot, "_status"),
params=params,
)
@query_params('master_timeout', 'timeout') @query_params("master_timeout", "timeout")
def verify_repository(self, repository, params=None): def verify_repository(self, repository, params=None):
""" """
Returns a list of nodes where repository was successfully verified or Returns a list of nodes where repository was successfully verified or
@@ -161,5 +178,6 @@ class SnapshotClient(NamespacedClient):
""" """
if repository in SKIP_IN_PATH: if repository in SKIP_IN_PATH:
raise ValueError("Empty value passed for a required argument 'repository'.") raise ValueError("Empty value passed for a required argument 'repository'.")
return self.transport.perform_request('POST', _make_path('_snapshot', return self.transport.perform_request(
repository, '_verify'), params=params) "POST", _make_path("_snapshot", repository, "_verify"), params=params
)
+19 -9
View File
@@ -1,8 +1,16 @@
from .utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH from .utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH
class TasksClient(NamespacedClient): class TasksClient(NamespacedClient):
@query_params('actions', 'detailed', 'group_by', 'nodes', @query_params(
'parent_task_id', 'wait_for_completion', 'timeout') "actions",
"detailed",
"group_by",
"nodes",
"parent_task_id",
"wait_for_completion",
"timeout",
)
def list(self, params=None): def list(self, params=None):
""" """
`<http://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html>`_ `<http://www.elastic.co/guide/en/elasticsearch/reference/current/tasks.html>`_
@@ -22,9 +30,9 @@ class TasksClient(NamespacedClient):
(default: false) (default: false)
:arg timeout: Maximum waiting time for `wait_for_completion` :arg timeout: Maximum waiting time for `wait_for_completion`
""" """
return self.transport.perform_request('GET', '/_tasks', params=params) return self.transport.perform_request("GET", "/_tasks", params=params)
@query_params('actions', 'nodes', 'parent_task_id') @query_params("actions", "nodes", "parent_task_id")
def cancel(self, task_id=None, params=None): def cancel(self, task_id=None, params=None):
""" """
@@ -41,10 +49,11 @@ class TasksClient(NamespacedClient):
:arg parent_task_id: Cancel tasks with specified parent task id :arg parent_task_id: Cancel tasks with specified parent task id
(node_id:task_number). Set to -1 to cancel all. (node_id:task_number). Set to -1 to cancel all.
""" """
return self.transport.perform_request('POST', _make_path('_tasks', return self.transport.perform_request(
task_id, '_cancel'), params=params) "POST", _make_path("_tasks", task_id, "_cancel"), params=params
)
@query_params('wait_for_completion', 'timeout') @query_params("wait_for_completion", "timeout")
def get(self, task_id=None, params=None): def get(self, task_id=None, params=None):
""" """
Retrieve information for a particular task. Retrieve information for a particular task.
@@ -55,5 +64,6 @@ class TasksClient(NamespacedClient):
(default: false) (default: false)
:arg timeout: Maximum waiting time for `wait_for_completion` :arg timeout: Maximum waiting time for `wait_for_completion`
""" """
return self.transport.perform_request('GET', _make_path('_tasks', return self.transport.perform_request(
task_id), params=params) "GET", _make_path("_tasks", task_id), params=params
)
+3 -2
View File
@@ -3,13 +3,14 @@ import sys
PY2 = sys.version_info[0] == 2 PY2 = sys.version_info[0] == 2
if PY2: if PY2:
string_types = basestring, string_types = (basestring,)
from urllib import quote_plus, urlencode, unquote from urllib import quote_plus, urlencode, unquote
from urlparse import urlparse from urlparse import urlparse
from itertools import imap as map from itertools import imap as map
from Queue import Queue from Queue import Queue
else: else:
string_types = str, bytes string_types = str, bytes
from urllib.parse import quote_plus, urlencode, urlparse, unquote from urllib.parse import quote_plus, urlencode, urlparse, unquote
map = map map = map
from queue import Queue from queue import Queue
-1
View File
@@ -1,4 +1,3 @@
from .base import Connection from .base import Connection
from .http_requests import RequestsHttpConnection from .http_requests import RequestsHttpConnection
from .http_urllib3 import Urllib3HttpConnection, create_ssl_context from .http_urllib3 import Urllib3HttpConnection, create_ssl_context
+73 -37
View File
@@ -1,4 +1,5 @@
import logging import logging
try: try:
import simplejson as json import simplejson as json
except ImportError: except ImportError:
@@ -6,12 +7,12 @@ except ImportError:
from ..exceptions import TransportError, HTTP_EXCEPTIONS from ..exceptions import TransportError, HTTP_EXCEPTIONS
logger = logging.getLogger('elasticsearch') logger = logging.getLogger("elasticsearch")
# create the elasticsearch.trace logger, but only set propagate to False if the # create the elasticsearch.trace logger, but only set propagate to False if the
# logger hasn't already been configured # logger hasn't already been configured
_tracer_already_configured = 'elasticsearch.trace' in logging.Logger.manager.loggerDict _tracer_already_configured = "elasticsearch.trace" in logging.Logger.manager.loggerDict
tracer = logging.getLogger('elasticsearch.trace') tracer = logging.getLogger("elasticsearch.trace")
if not _tracer_already_configured: if not _tracer_already_configured:
tracer.propagate = False tracer.propagate = False
@@ -24,32 +25,43 @@ class Connection(object):
Also responsible for logging. Also responsible for logging.
""" """
def __init__(self, host='localhost', port=9200, use_ssl=False, url_prefix='', timeout=10, **kwargs):
def __init__(
self,
host="localhost",
port=9200,
use_ssl=False,
url_prefix="",
timeout=10,
**kwargs
):
""" """
:arg host: hostname of the node (default: localhost) :arg host: hostname of the node (default: localhost)
:arg port: port to use (integer, default: 9200) :arg port: port to use (integer, default: 9200)
:arg url_prefix: optional url prefix for elasticsearch :arg url_prefix: optional url prefix for elasticsearch
:arg timeout: default timeout in seconds (float, default: 10) :arg timeout: default timeout in seconds (float, default: 10)
""" """
scheme = kwargs.get('scheme', 'http') scheme = kwargs.get("scheme", "http")
if use_ssl or scheme == 'https': if use_ssl or scheme == "https":
scheme = 'https' scheme = "https"
use_ssl = True use_ssl = True
self.use_ssl = use_ssl self.use_ssl = use_ssl
self.host = '%s://%s:%s' % (scheme, host, port) self.host = "%s://%s:%s" % (scheme, host, port)
if url_prefix: if url_prefix:
url_prefix = '/' + url_prefix.strip('/') url_prefix = "/" + url_prefix.strip("/")
self.url_prefix = url_prefix self.url_prefix = url_prefix
self.timeout = timeout self.timeout = timeout
def __repr__(self): def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.host) return "<%s: %s>" % (self.__class__.__name__, self.host)
def _pretty_json(self, data): def _pretty_json(self, data):
# pretty JSON in tracer curl logs # pretty JSON in tracer curl logs
try: try:
return json.dumps(json.loads(data), sort_keys=True, indent=2, separators=(',', ': ')).replace("'", r'\u0027') return json.dumps(
json.loads(data), sort_keys=True, indent=2, separators=(",", ": ")
).replace("'", r"\u0027")
except (ValueError, TypeError): except (ValueError, TypeError):
# non-json data or a bulk request # non-json data or a bulk request
return data return data
@@ -59,17 +71,28 @@ class Connection(object):
return return
# include pretty in trace curls # include pretty in trace curls
path = path.replace('?', '?pretty&', 1) if '?' in path else path + '?pretty' path = path.replace("?", "?pretty&", 1) if "?" in path else path + "?pretty"
if self.url_prefix: if self.url_prefix:
path = path.replace(self.url_prefix, '', 1) path = path.replace(self.url_prefix, "", 1)
tracer.info("curl %s-X%s 'http://localhost:9200%s' -d '%s'", tracer.info(
"-H 'Content-Type: application/json' " if body else '', "curl %s-X%s 'http://localhost:9200%s' -d '%s'",
method, path, self._pretty_json(body) if body else '') "-H 'Content-Type: application/json' " if body else "",
method,
path,
self._pretty_json(body) if body else "",
)
if tracer.isEnabledFor(logging.DEBUG): if tracer.isEnabledFor(logging.DEBUG):
tracer.debug('#[%s] (%.3fs)\n#%s', status_code, duration, self._pretty_json(response).replace('\n', '\n#') if response else '') tracer.debug(
"#[%s] (%.3fs)\n#%s",
status_code,
duration,
self._pretty_json(response).replace("\n", "\n#") if response else "",
)
def log_request_success(self, method, full_url, path, body, status_code, response, duration): def log_request_success(
self, method, full_url, path, body, status_code, response, duration
):
""" Log a successful API call. """ """ Log a successful API call. """
# TODO: optionally pass in params instead of full_url and do urlencode only when needed # TODO: optionally pass in params instead of full_url and do urlencode only when needed
@@ -77,43 +100,56 @@ class Connection(object):
# TODO: find a better way to avoid (de)encoding the body back and forth # TODO: find a better way to avoid (de)encoding the body back and forth
if body: if body:
try: try:
body = body.decode('utf-8', 'ignore') body = body.decode("utf-8", "ignore")
except AttributeError: except AttributeError:
pass pass
logger.info( logger.info(
'%s %s [status:%s request:%.3fs]', method, full_url, "%s %s [status:%s request:%.3fs]", method, full_url, status_code, duration
status_code, duration
) )
logger.debug('> %s', body) logger.debug("> %s", body)
logger.debug('< %s', response) logger.debug("< %s", response)
self._log_trace(method, path, body, status_code, response, duration) self._log_trace(method, path, body, status_code, response, duration)
def log_request_fail(self, method, full_url, path, body, duration, status_code=None, response=None, exception=None): def log_request_fail(
self,
method,
full_url,
path,
body,
duration,
status_code=None,
response=None,
exception=None,
):
""" Log an unsuccessful API call. """ """ Log an unsuccessful API call. """
# do not log 404s on HEAD requests # do not log 404s on HEAD requests
if method == 'HEAD' and status_code == 404: if method == "HEAD" and status_code == 404:
return return
logger.warning( logger.warning(
'%s %s [status:%s request:%.3fs]', method, full_url, "%s %s [status:%s request:%.3fs]",
status_code or 'N/A', duration, exc_info=exception is not None method,
full_url,
status_code or "N/A",
duration,
exc_info=exception is not None,
) )
# body has already been serialized to utf-8, deserialize it for logging # body has already been serialized to utf-8, deserialize it for logging
# TODO: find a better way to avoid (de)encoding the body back and forth # TODO: find a better way to avoid (de)encoding the body back and forth
if body: if body:
try: try:
body = body.decode('utf-8', 'ignore') body = body.decode("utf-8", "ignore")
except AttributeError: except AttributeError:
pass pass
logger.debug('> %s', body) logger.debug("> %s", body)
self._log_trace(method, path, body, status_code, response, duration) self._log_trace(method, path, body, status_code, response, duration)
if response is not None: if response is not None:
logger.debug('< %s', response) logger.debug("< %s", response)
def _raise_error(self, status_code, raw_data): def _raise_error(self, status_code, raw_data):
""" Locate appropriate exception and raise it. """ """ Locate appropriate exception and raise it. """
@@ -122,12 +158,12 @@ class Connection(object):
try: try:
if raw_data: if raw_data:
additional_info = json.loads(raw_data) additional_info = json.loads(raw_data)
error_message = additional_info.get('error', error_message) error_message = additional_info.get("error", error_message)
if isinstance(error_message, dict) and 'type' in error_message: if isinstance(error_message, dict) and "type" in error_message:
error_message = error_message['type'] error_message = error_message["type"]
except (ValueError, TypeError) as err: except (ValueError, TypeError) as err:
logger.warning('Undecodable raw error response from server: %s', err) logger.warning("Undecodable raw error response from server: %s", err)
raise HTTP_EXCEPTIONS.get(status_code, TransportError)(status_code, error_message, additional_info)
raise HTTP_EXCEPTIONS.get(status_code, TransportError)(
status_code, error_message, additional_info
)
+85 -25
View File
@@ -1,15 +1,23 @@
import time import time
import warnings import warnings
try: try:
import requests import requests
REQUESTS_AVAILABLE = True REQUESTS_AVAILABLE = True
except ImportError: except ImportError:
REQUESTS_AVAILABLE = False REQUESTS_AVAILABLE = False
from .base import Connection from .base import Connection
from ..exceptions import ConnectionError, ImproperlyConfigured, ConnectionTimeout, SSLError from ..exceptions import (
ConnectionError,
ImproperlyConfigured,
ConnectionTimeout,
SSLError,
)
from ..compat import urlencode, string_types from ..compat import urlencode, string_types
class RequestsHttpConnection(Connection): class RequestsHttpConnection(Connection):
""" """
Connection using the `requests` library. Connection using the `requests` library.
@@ -27,25 +35,43 @@ class RequestsHttpConnection(Connection):
separate cert and key files (client_cert will contain only the cert) separate cert and key files (client_cert will contain only the cert)
:arg headers: any custom http headers to be add to requests :arg headers: any custom http headers to be add to requests
""" """
def __init__(self, host='localhost', port=9200, http_auth=None,
use_ssl=False, verify_certs=True, ssl_show_warn=True, ca_certs=None, client_cert=None,
client_key=None, headers=None, **kwargs):
if not REQUESTS_AVAILABLE:
raise ImproperlyConfigured("Please install requests to use RequestsHttpConnection.")
super(RequestsHttpConnection, self).__init__(host=host, port=port, use_ssl=use_ssl, **kwargs) def __init__(
self,
host="localhost",
port=9200,
http_auth=None,
use_ssl=False,
verify_certs=True,
ssl_show_warn=True,
ca_certs=None,
client_cert=None,
client_key=None,
headers=None,
**kwargs
):
if not REQUESTS_AVAILABLE:
raise ImproperlyConfigured(
"Please install requests to use RequestsHttpConnection."
)
super(RequestsHttpConnection, self).__init__(
host=host, port=port, use_ssl=use_ssl, **kwargs
)
self.session = requests.Session() self.session = requests.Session()
self.session.headers = headers or {} self.session.headers = headers or {}
self.session.headers.setdefault('content-type', 'application/json') self.session.headers.setdefault("content-type", "application/json")
if http_auth is not None: if http_auth is not None:
if isinstance(http_auth, (tuple, list)): if isinstance(http_auth, (tuple, list)):
http_auth = tuple(http_auth) http_auth = tuple(http_auth)
elif isinstance(http_auth, string_types): elif isinstance(http_auth, string_types):
http_auth = tuple(http_auth.split(':', 1)) http_auth = tuple(http_auth.split(":", 1))
self.session.auth = http_auth self.session.auth = http_auth
self.base_url = 'http%s://%s:%d%s' % ( self.base_url = "http%s://%s:%d%s" % (
's' if self.use_ssl else '', "s" if self.use_ssl else "",
host, port, self.url_prefix host,
port,
self.url_prefix,
) )
self.session.verify = verify_certs self.session.verify = verify_certs
if not client_key: if not client_key:
@@ -55,42 +81,76 @@ class RequestsHttpConnection(Connection):
self.session.cert = (client_cert, client_key) self.session.cert = (client_cert, client_key)
if ca_certs: if ca_certs:
if not verify_certs: if not verify_certs:
raise ImproperlyConfigured("You cannot pass CA certificates when verify SSL is off.") raise ImproperlyConfigured(
"You cannot pass CA certificates when verify SSL is off."
)
self.session.verify = ca_certs self.session.verify = ca_certs
if self.use_ssl and not verify_certs and ssl_show_warn: if self.use_ssl and not verify_certs and ssl_show_warn:
warnings.warn( warnings.warn(
'Connecting to %s using SSL with verify_certs=False is insecure.' % self.base_url) "Connecting to %s using SSL with verify_certs=False is insecure."
% self.base_url
)
def perform_request(self, method, url, params=None, body=None, timeout=None, ignore=(), headers=None): def perform_request(
self, method, url, params=None, body=None, timeout=None, ignore=(), headers=None
):
url = self.base_url + url url = self.base_url + url
if params: if params:
url = '%s?%s' % (url, urlencode(params or {})) url = "%s?%s" % (url, urlencode(params or {}))
start = time.time() start = time.time()
request = requests.Request(method=method, headers=headers, url=url, data=body) request = requests.Request(method=method, headers=headers, url=url, data=body)
prepared_request = self.session.prepare_request(request) prepared_request = self.session.prepare_request(request)
settings = self.session.merge_environment_settings(prepared_request.url, {}, None, None, None) settings = self.session.merge_environment_settings(
send_kwargs = {'timeout': timeout or self.timeout} prepared_request.url, {}, None, None, None
)
send_kwargs = {"timeout": timeout or self.timeout}
send_kwargs.update(settings) send_kwargs.update(settings)
try: try:
response = self.session.send(prepared_request, **send_kwargs) response = self.session.send(prepared_request, **send_kwargs)
duration = time.time() - start duration = time.time() - start
raw_data = response.text raw_data = response.text
except Exception as e: except Exception as e:
self.log_request_fail(method, url, prepared_request.path_url, body, time.time() - start, exception=e) self.log_request_fail(
method,
url,
prepared_request.path_url,
body,
time.time() - start,
exception=e,
)
if isinstance(e, requests.exceptions.SSLError): if isinstance(e, requests.exceptions.SSLError):
raise SSLError('N/A', str(e), e) raise SSLError("N/A", str(e), e)
if isinstance(e, requests.Timeout): if isinstance(e, requests.Timeout):
raise ConnectionTimeout('TIMEOUT', str(e), e) raise ConnectionTimeout("TIMEOUT", str(e), e)
raise ConnectionError('N/A', str(e), e) raise ConnectionError("N/A", str(e), e)
# raise errors based on http status codes, let the client handle those if needed # raise errors based on http status codes, let the client handle those if needed
if not (200 <= response.status_code < 300) and response.status_code not in ignore: if (
self.log_request_fail(method, url, response.request.path_url, body, duration, response.status_code, raw_data) not (200 <= response.status_code < 300)
and response.status_code not in ignore
):
self.log_request_fail(
method,
url,
response.request.path_url,
body,
duration,
response.status_code,
raw_data,
)
self._raise_error(response.status_code, raw_data) self._raise_error(response.status_code, raw_data)
self.log_request_success(method, url, response.request.path_url, body, response.status_code, raw_data, duration) self.log_request_success(
method,
url,
response.request.path_url,
body,
response.status_code,
raw_data,
duration,
)
return response.status_code, response.headers, raw_data return response.status_code, response.headers, raw_data
+1 -1
View File
@@ -12,6 +12,7 @@ class PoolingConnection(Connection):
``_make_connection`` method that constructs a new connection and returns ``_make_connection`` method that constructs a new connection and returns
it. it.
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self._free_connections = queue.Queue() self._free_connections = queue.Queue()
super(PoolingConnection, self).__init__(*args, **kwargs) super(PoolingConnection, self).__init__(*args, **kwargs)
@@ -30,4 +31,3 @@ class PoolingConnection(Connection):
Explicitly close connection Explicitly close connection
""" """
pass pass
+31 -14
View File
@@ -10,7 +10,8 @@ except ImportError:
from .exceptions import ImproperlyConfigured from .exceptions import ImproperlyConfigured
logger = logging.getLogger('elasticsearch') logger = logging.getLogger("elasticsearch")
class ConnectionSelector(object): class ConnectionSelector(object):
""" """
@@ -30,6 +31,7 @@ class ConnectionSelector(object):
only select connections from it's own zones and only fall back to other only select connections from it's own zones and only fall back to other
connections where there would be none in it's zones. connections where there would be none in it's zones.
""" """
def __init__(self, opts): def __init__(self, opts):
""" """
:arg opts: dictionary of connection instances and their options :arg opts: dictionary of connection instances and their options
@@ -49,6 +51,7 @@ class RandomSelector(ConnectionSelector):
""" """
Select a connection at random Select a connection at random
""" """
def select(self, connections): def select(self, connections):
return random.choice(connections) return random.choice(connections)
@@ -57,15 +60,17 @@ class RoundRobinSelector(ConnectionSelector):
""" """
Selector using round-robin. Selector using round-robin.
""" """
def __init__(self, opts): def __init__(self, opts):
super(RoundRobinSelector, self).__init__(opts) super(RoundRobinSelector, self).__init__(opts)
self.data = threading.local() self.data = threading.local()
def select(self, connections): def select(self, connections):
self.data.rr = getattr(self.data, 'rr', -1) + 1 self.data.rr = getattr(self.data, "rr", -1) + 1
self.data.rr %= len(connections) self.data.rr %= len(connections)
return connections[self.data.rr] return connections[self.data.rr]
class ConnectionPool(object): class ConnectionPool(object):
""" """
Container holding the :class:`~elasticsearch.Connection` instances, Container holding the :class:`~elasticsearch.Connection` instances,
@@ -88,8 +93,16 @@ class ConnectionPool(object):
live pool. A connection that has been previously marked as dead and live pool. A connection that has been previously marked as dead and
succeeds will be marked as live (its fail count will be deleted). succeeds will be marked as live (its fail count will be deleted).
""" """
def __init__(self, connections, dead_timeout=60, timeout_cutoff=5,
selector_class=RoundRobinSelector, randomize_hosts=True, **kwargs): def __init__(
self,
connections,
dead_timeout=60,
timeout_cutoff=5,
selector_class=RoundRobinSelector,
randomize_hosts=True,
**kwargs
):
""" """
:arg connections: list of tuples containing the :arg connections: list of tuples containing the
:class:`~elasticsearch.Connection` instance and it's options :class:`~elasticsearch.Connection` instance and it's options
@@ -103,8 +116,9 @@ class ConnectionPool(object):
avoid dog piling effect across processes avoid dog piling effect across processes
""" """
if not connections: if not connections:
raise ImproperlyConfigured("No defined connections, you need to " raise ImproperlyConfigured(
"specify at least one host.") "No defined connections, you need to " "specify at least one host."
)
self.connection_opts = connections self.connection_opts = connections
self.connections = [c for (c, opts) in connections] self.connections = [c for (c, opts) in connections]
# remember original connection list for resurrect(force=True) # remember original connection list for resurrect(force=True)
@@ -144,8 +158,10 @@ class ConnectionPool(object):
timeout = self.dead_timeout * 2 ** min(dead_count - 1, self.timeout_cutoff) timeout = self.dead_timeout * 2 ** min(dead_count - 1, self.timeout_cutoff)
self.dead.put((now + timeout, connection)) self.dead.put((now + timeout, connection))
logger.warning( logger.warning(
'Connection %r has failed for %i times in a row, putting on %i second timeout.', "Connection %r has failed for %i times in a row, putting on %i second timeout.",
connection, dead_count, timeout connection,
dead_count,
timeout,
) )
def mark_live(self, connection): def mark_live(self, connection):
@@ -200,7 +216,7 @@ class ConnectionPool(object):
# either we were forced or the connection is elligible to be retried # either we were forced or the connection is elligible to be retried
self.connections.append(connection) self.connections.append(connection)
logger.info('Resurrecting connection %r (force=%s).', connection, force) logger.info("Resurrecting connection %r (force=%s).", connection, force)
return connection return connection
def get_connection(self): def get_connection(self):
@@ -235,15 +251,17 @@ class ConnectionPool(object):
for conn in self.orig_connections: for conn in self.orig_connections:
conn.close() conn.close()
class DummyConnectionPool(ConnectionPool): class DummyConnectionPool(ConnectionPool):
def __init__(self, connections, **kwargs): def __init__(self, connections, **kwargs):
if len(connections) != 1: if len(connections) != 1:
raise ImproperlyConfigured("DummyConnectionPool needs exactly one " raise ImproperlyConfigured(
"connection defined.") "DummyConnectionPool needs exactly one " "connection defined."
)
# we need connection opts for sniffing logic # we need connection opts for sniffing logic
self.connection_opts = connections self.connection_opts = connections
self.connection = connections[0][0] self.connection = connections[0][0]
self.connections = (self.connection, ) self.connections = (self.connection,)
def get_connection(self): def get_connection(self):
return self.connection return self.connection
@@ -256,6 +274,5 @@ class DummyConnectionPool(ConnectionPool):
def _noop(self, *args, **kwargs): def _noop(self, *args, **kwargs):
pass pass
mark_dead = mark_live = resurrect = _noop mark_dead = mark_live = resurrect = _noop
+39 -16
View File
@@ -1,7 +1,16 @@
__all__ = [ __all__ = [
'ImproperlyConfigured', 'ElasticsearchException', 'SerializationError', "ImproperlyConfigured",
'TransportError', 'NotFoundError', 'ConflictError', 'RequestError', 'ConnectionError', "ElasticsearchException",
'SSLError', 'ConnectionTimeout', 'AuthenticationException', 'AuthorizationException' "SerializationError",
"TransportError",
"NotFoundError",
"ConflictError",
"RequestError",
"ConnectionError",
"SSLError",
"ConnectionTimeout",
"AuthenticationException",
"AuthorizationException",
] ]
@@ -31,6 +40,7 @@ class TransportError(ElasticsearchException):
an actual connection error happens; in that case the ``status_code`` will an actual connection error happens; in that case the ``status_code`` will
be set to ``'N/A'``. be set to ``'N/A'``.
""" """
@property @property
def status_code(self): def status_code(self):
""" """
@@ -53,20 +63,28 @@ class TransportError(ElasticsearchException):
return self.args[2] return self.args[2]
def __str__(self): def __str__(self):
cause = '' cause = ""
try: try:
if self.info and 'error' in self.info: if self.info and "error" in self.info:
if isinstance(self.info['error'], dict): if isinstance(self.info["error"], dict):
root_cause = self.info['error']['root_cause'][0] root_cause = self.info["error"]["root_cause"][0]
cause = ', '.join(filter(None, [repr(root_cause['reason']), root_cause.get('resource.id'), cause = ", ".join(
root_cause.get('resource.type')])) filter(
None,
[
repr(root_cause["reason"]),
root_cause.get("resource.id"),
root_cause.get("resource.type"),
],
)
)
else: else:
cause = repr(self.info['error']) cause = repr(self.info["error"])
except LookupError: except LookupError:
pass pass
msg = ', '.join(filter(None, [str(self.status_code), repr(self.error), cause])) msg = ", ".join(filter(None, [str(self.status_code), repr(self.error), cause]))
return '%s(%s)' % (self.__class__.__name__, msg) return "%s(%s)" % (self.__class__.__name__, msg)
class ConnectionError(TransportError): class ConnectionError(TransportError):
@@ -77,8 +95,11 @@ class ConnectionError(TransportError):
""" """
def __str__(self): def __str__(self):
return 'ConnectionError(%s) caused by: %s(%s)' % ( return "ConnectionError(%s) caused by: %s(%s)" % (
self.error, self.info.__class__.__name__, self.info) self.error,
self.info.__class__.__name__,
self.info,
)
class SSLError(ConnectionError): class SSLError(ConnectionError):
@@ -89,8 +110,10 @@ class ConnectionTimeout(ConnectionError):
""" A network timeout. Doesn't cause a node retry by default. """ """ A network timeout. Doesn't cause a node retry by default. """
def __str__(self): def __str__(self):
return 'ConnectionTimeout caused by - %s(%s)' % ( return "ConnectionTimeout caused by - %s(%s)" % (
self.info.__class__.__name__, self.info) self.info.__class__.__name__,
self.info,
)
class NotFoundError(TransportError): class NotFoundError(TransportError):
-4
View File
@@ -1,8 +1,4 @@
from .errors import BulkIndexError, ScanError from .errors import BulkIndexError, ScanError
from .actions import expand_action, streaming_bulk, bulk, parallel_bulk from .actions import expand_action, streaming_bulk, bulk, parallel_bulk
from .actions import scan, reindex from .actions import scan, reindex
from .actions import _chunk_actions, _process_bulk_chunk from .actions import _chunk_actions, _process_bulk_chunk
+1 -1
View File
@@ -437,7 +437,7 @@ def scan(
scroll_id = resp.get("_scroll_id") scroll_id = resp.get("_scroll_id")
try: try:
while scroll_id and resp['hits']['hits']: while scroll_id and resp["hits"]["hits"]:
for hit in resp["hits"]["hits"]: for hit in resp["hits"]["hits"]:
yield hit yield hit
-2
View File
@@ -1,5 +1,3 @@
from ..exceptions import ElasticsearchException from ..exceptions import ElasticsearchException
+17 -13
View File
@@ -1,5 +1,6 @@
import time import time
import os import os
try: try:
# python 2.6 # python 2.6
from unittest2 import TestCase, SkipTest from unittest2 import TestCase, SkipTest
@@ -9,33 +10,37 @@ except ImportError:
from elasticsearch import Elasticsearch from elasticsearch import Elasticsearch
from elasticsearch.exceptions import ConnectionError from elasticsearch.exceptions import ConnectionError
def get_test_client(nowait=False, **kwargs): def get_test_client(nowait=False, **kwargs):
# construct kwargs from the environment # construct kwargs from the environment
kw = {'timeout': 30} kw = {"timeout": 30}
if 'TEST_ES_CONNECTION' in os.environ: if "TEST_ES_CONNECTION" in os.environ:
from elasticsearch import connection from elasticsearch import connection
kw['connection_class'] = getattr(connection, os.environ['TEST_ES_CONNECTION'])
kw["connection_class"] = getattr(connection, os.environ["TEST_ES_CONNECTION"])
kw.update(kwargs) kw.update(kwargs)
client = Elasticsearch([os.environ.get('TEST_ES_SERVER', {})], **kw) client = Elasticsearch([os.environ.get("TEST_ES_SERVER", {})], **kw)
# wait for yellow status # wait for yellow status
for _ in range(1 if nowait else 100): for _ in range(1 if nowait else 100):
try: try:
client.cluster.health(wait_for_status='yellow') client.cluster.health(wait_for_status="yellow")
return client return client
except ConnectionError: except ConnectionError:
time.sleep(.1) time.sleep(0.1)
else: else:
# timeout # timeout
raise SkipTest("Elasticsearch failed to start.") raise SkipTest("Elasticsearch failed to start.")
def _get_version(version_string): def _get_version(version_string):
if '.' not in version_string: if "." not in version_string:
return () return ()
version = version_string.strip().split('.') version = version_string.strip().split(".")
return tuple(int(v) if v.isdigit() else 999 for v in version) return tuple(int(v) if v.isdigit() else 999 for v in version)
class ElasticsearchTestCase(TestCase): class ElasticsearchTestCase(TestCase):
@staticmethod @staticmethod
def _get_client(): def _get_client():
@@ -48,13 +53,12 @@ class ElasticsearchTestCase(TestCase):
def tearDown(self): def tearDown(self):
super(ElasticsearchTestCase, self).tearDown() super(ElasticsearchTestCase, self).tearDown()
self.client.indices.delete(index='*', ignore=404) self.client.indices.delete(index="*", ignore=404)
self.client.indices.delete_template(name='*', ignore=404) self.client.indices.delete_template(name="*", ignore=404)
@property @property
def es_version(self): def es_version(self):
if not hasattr(self, '_es_version'): if not hasattr(self, "_es_version"):
version_string = self.client.info()['version']['number'] version_string = self.client.info()["version"]["number"]
self._es_version = _get_version(version_string) self._es_version = _get_version(version_string)
return self._es_version return self._es_version
+16 -12
View File
@@ -9,8 +9,9 @@ from decimal import Decimal
from .exceptions import SerializationError, ImproperlyConfigured from .exceptions import SerializationError, ImproperlyConfigured
from .compat import string_types from .compat import string_types
class TextSerializer(object): class TextSerializer(object):
mimetype = 'text/plain' mimetype = "text/plain"
def loads(self, s): def loads(self, s):
return s return s
@@ -19,10 +20,11 @@ class TextSerializer(object):
if isinstance(data, string_types): if isinstance(data, string_types):
return data return data
raise SerializationError('Cannot serialize %r into text.' % data) raise SerializationError("Cannot serialize %r into text." % data)
class JSONSerializer(object): class JSONSerializer(object):
mimetype = 'application/json' mimetype = "application/json"
def default(self, data): def default(self, data):
if isinstance(data, (date, datetime)): if isinstance(data, (date, datetime)):
@@ -46,25 +48,26 @@ class JSONSerializer(object):
try: try:
return json.dumps( return json.dumps(
data, data, default=self.default, ensure_ascii=False, separators=(",", ":")
default=self.default,
ensure_ascii=False,
separators=(',', ':'),
) )
except (ValueError, TypeError) as e: except (ValueError, TypeError) as e:
raise SerializationError(data, e) raise SerializationError(data, e)
DEFAULT_SERIALIZERS = { DEFAULT_SERIALIZERS = {
JSONSerializer.mimetype: JSONSerializer(), JSONSerializer.mimetype: JSONSerializer(),
TextSerializer.mimetype: TextSerializer(), TextSerializer.mimetype: TextSerializer(),
} }
class Deserializer(object): class Deserializer(object):
def __init__(self, serializers, default_mimetype='application/json'): def __init__(self, serializers, default_mimetype="application/json"):
try: try:
self.default = serializers[default_mimetype] self.default = serializers[default_mimetype]
except KeyError: except KeyError:
raise ImproperlyConfigured('Cannot find default serializer (%s)' % default_mimetype) raise ImproperlyConfigured(
"Cannot find default serializer (%s)" % default_mimetype
)
self.serializers = serializers self.serializers = serializers
def loads(self, s, mimetype=None): def loads(self, s, mimetype=None):
@@ -72,11 +75,12 @@ class Deserializer(object):
deserializer = self.default deserializer = self.default
else: else:
# split out charset # split out charset
mimetype, _, _ = mimetype.partition(';') mimetype, _, _ = mimetype.partition(";")
try: try:
deserializer = self.serializers[mimetype] deserializer = self.serializers[mimetype]
except KeyError: except KeyError:
raise SerializationError('Unknown mimetype, unable to deserialize: %s' % mimetype) raise SerializationError(
"Unknown mimetype, unable to deserialize: %s" % mimetype
)
return deserializer.loads(s) return deserializer.loads(s)
+72 -34
View File
@@ -4,8 +4,12 @@ from itertools import chain
from .connection import Urllib3HttpConnection from .connection import Urllib3HttpConnection
from .connection_pool import ConnectionPool, DummyConnectionPool from .connection_pool import ConnectionPool, DummyConnectionPool
from .serializer import JSONSerializer, Deserializer, DEFAULT_SERIALIZERS from .serializer import JSONSerializer, Deserializer, DEFAULT_SERIALIZERS
from .exceptions import ConnectionError, TransportError, SerializationError, \ from .exceptions import (
ConnectionTimeout ConnectionError,
TransportError,
SerializationError,
ConnectionTimeout,
)
def get_host_info(node_info, host): def get_host_info(node_info, host):
@@ -23,10 +27,11 @@ def get_host_info(node_info, host):
:arg host: connection information (host, port) extracted from the node info :arg host: connection information (host, port) extracted from the node info
""" """
# ignore master only nodes # ignore master only nodes
if node_info.get('roles', []) == ['master']: if node_info.get("roles", []) == ["master"]:
return None return None
return host return host
class Transport(object): class Transport(object):
""" """
Encapsulation of transport-related to logic. Handles instantiation of the Encapsulation of transport-related to logic. Handles instantiation of the
@@ -34,12 +39,26 @@ class Transport(object):
Main interface is the `perform_request` method. Main interface is the `perform_request` method.
""" """
def __init__(self, hosts, connection_class=Urllib3HttpConnection,
connection_pool_class=ConnectionPool, host_info_callback=get_host_info, def __init__(
sniff_on_start=False, sniffer_timeout=None, sniff_timeout=.1, self,
sniff_on_connection_fail=False, serializer=JSONSerializer(), serializers=None, hosts,
default_mimetype='application/json', max_retries=3, retry_on_status=(502, 503, 504, ), connection_class=Urllib3HttpConnection,
retry_on_timeout=False, send_get_body_as='GET', **kwargs): connection_pool_class=ConnectionPool,
host_info_callback=get_host_info,
sniff_on_start=False,
sniffer_timeout=None,
sniff_timeout=0.1,
sniff_on_connection_fail=False,
serializer=JSONSerializer(),
serializers=None,
default_mimetype="application/json",
max_retries=3,
retry_on_status=(502, 503, 504),
retry_on_timeout=False,
send_get_body_as="GET",
**kwargs
):
""" """
:arg hosts: list of dictionaries, each containing keyword arguments to :arg hosts: list of dictionaries, each containing keyword arguments to
create a `connection_class` instance create a `connection_class` instance
@@ -143,7 +162,7 @@ class Transport(object):
# if this is not the initial setup look at the existing connection # if this is not the initial setup look at the existing connection
# options and identify connections that haven't changed and can be # options and identify connections that haven't changed and can be
# kept around. # kept around.
if hasattr(self, 'connection_pool'): if hasattr(self, "connection_pool"):
for (connection, old_host) in self.connection_pool.connection_opts: for (connection, old_host) in self.connection_pool.connection_opts:
if old_host == host: if old_host == host:
return connection return connection
@@ -152,6 +171,7 @@ class Transport(object):
kwargs = self.kwargs.copy() kwargs = self.kwargs.copy()
kwargs.update(host) kwargs.update(host)
return self.connection_class(**kwargs) return self.connection_class(**kwargs)
connections = map(_create_connection, hosts) connections = map(_create_connection, hosts)
connections = list(zip(connections, hosts)) connections = list(zip(connections, hosts))
@@ -159,7 +179,9 @@ class Transport(object):
self.connection_pool = DummyConnectionPool(connections) self.connection_pool = DummyConnectionPool(connections)
else: else:
# pass the hosts dicts to the connection pool to optionally extract parameters from # pass the hosts dicts to the connection pool to optionally extract parameters from
self.connection_pool = self.connection_pool_class(connections, **self.kwargs) self.connection_pool = self.connection_pool_class(
connections, **self.kwargs
)
def get_connection(self): def get_connection(self):
""" """
@@ -194,9 +216,13 @@ class Transport(object):
try: try:
# use small timeout for the sniffing request, should be a fast api call # use small timeout for the sniffing request, should be a fast api call
_, headers, node_info = c.perform_request( _, headers, node_info = c.perform_request(
'GET', '/_nodes/_all/http', "GET",
timeout=self.sniff_timeout if not initial else None) "/_nodes/_all/http",
node_info = self.deserializer.loads(node_info, headers.get('content-type')) timeout=self.sniff_timeout if not initial else None,
)
node_info = self.deserializer.loads(
node_info, headers.get("content-type")
)
break break
except (ConnectionError, SerializationError): except (ConnectionError, SerializationError):
pass pass
@@ -207,18 +233,18 @@ class Transport(object):
self.last_sniff = previous_sniff self.last_sniff = previous_sniff
raise raise
return list(node_info['nodes'].values()) return list(node_info["nodes"].values())
def _get_host_info(self, host_info): def _get_host_info(self, host_info):
host = {} host = {}
address = host_info.get('http', {}).get('publish_address') address = host_info.get("http", {}).get("publish_address")
# malformed or no address given # malformed or no address given
if not address or ':' not in address: if not address or ":" not in address:
return None return None
host['host'], host['port'] = address.rsplit(':', 1) host["host"], host["port"] = address.rsplit(":", 1)
host['port'] = int(host['port']) host["port"] = int(host["port"])
return self.host_info_callback(host_info, host) return self.host_info_callback(host_info, host)
@@ -239,7 +265,9 @@ class Transport(object):
# we weren't able to get any nodes or host_info_callback blocked all - # we weren't able to get any nodes or host_info_callback blocked all -
# raise error. # raise error.
if not hosts: if not hosts:
raise TransportError("N/A", "Unable to sniff hosts - no viable hosts found.") raise TransportError(
"N/A", "Unable to sniff hosts - no viable hosts found."
)
self.set_connections(hosts) self.set_connections(hosts)
@@ -280,21 +308,21 @@ class Transport(object):
body = self.serializer.dumps(body) body = self.serializer.dumps(body)
# some clients or environments don't support sending GET with body # some clients or environments don't support sending GET with body
if method in ('HEAD', 'GET') and self.send_get_body_as != 'GET': if method in ("HEAD", "GET") and self.send_get_body_as != "GET":
# send it as post instead # send it as post instead
if self.send_get_body_as == 'POST': if self.send_get_body_as == "POST":
method = 'POST' method = "POST"
# or as source parameter # or as source parameter
elif self.send_get_body_as == 'source': elif self.send_get_body_as == "source":
if params is None: if params is None:
params = {} params = {}
params['source'] = body params["source"] = body
body = None body = None
if body is not None: if body is not None:
try: try:
body = body.encode('utf-8', 'surrogatepass') body = body.encode("utf-8", "surrogatepass")
except (UnicodeDecodeError, AttributeError): except (UnicodeDecodeError, AttributeError):
# bytes/str - no need to re-encode # bytes/str - no need to re-encode
pass pass
@@ -302,10 +330,10 @@ class Transport(object):
ignore = () ignore = ()
timeout = None timeout = None
if params: if params:
timeout = params.pop('request_timeout', None) timeout = params.pop("request_timeout", None)
ignore = params.pop('ignore', ()) ignore = params.pop("ignore", ())
if isinstance(ignore, int): if isinstance(ignore, int):
ignore = (ignore, ) ignore = (ignore,)
for attempt in range(self.max_retries + 1): for attempt in range(self.max_retries + 1):
connection = self.get_connection() connection = self.get_connection()
@@ -313,12 +341,20 @@ class Transport(object):
try: try:
# add a delay before attempting the next retry # add a delay before attempting the next retry
# 0, 1, 3, 7, etc... # 0, 1, 3, 7, etc...
delay = 2**attempt - 1 delay = 2 ** attempt - 1
time.sleep(delay) time.sleep(delay)
status, headers_response, data = connection.perform_request(method, url, params, body, headers=headers, ignore=ignore, timeout=timeout) status, headers_response, data = connection.perform_request(
method,
url,
params,
body,
headers=headers,
ignore=ignore,
timeout=timeout,
)
except TransportError as e: except TransportError as e:
if method == 'HEAD' and e.status_code == 404: if method == "HEAD" and e.status_code == 404:
return False return False
retry = False retry = False
@@ -342,11 +378,13 @@ class Transport(object):
# connection didn't fail, confirm it's live status # connection didn't fail, confirm it's live status
self.connection_pool.mark_live(connection) self.connection_pool.mark_live(connection)
if method == 'HEAD': if method == "HEAD":
return 200 <= status < 300 return 200 <= status < 300
if data: if data:
data = self.deserializer.loads(data, headers_response.get('content-type')) data = self.deserializer.loads(
data, headers_response.get("content-type")
)
return data return data
def close(self): def close(self):
+89 -86
View File
@@ -14,65 +14,62 @@ from elasticsearch import Elasticsearch
from elasticsearch.exceptions import TransportError from elasticsearch.exceptions import TransportError
from elasticsearch.helpers import bulk, streaming_bulk from elasticsearch.helpers import bulk, streaming_bulk
def create_git_index(client, index): def create_git_index(client, index):
# we will use user on several places # we will use user on several places
user_mapping = { user_mapping = {
'properties': { "properties": {
'name': { "name": {"type": "text", "fields": {"keyword": {"type": "keyword"}}}
'type': 'text',
'fields': {
'keyword': {'type': 'keyword'},
}
} }
}
} }
create_index_body = { create_index_body = {
'settings': { "settings": {
# just one shard, no replicas for testing # just one shard, no replicas for testing
'number_of_shards': 1, "number_of_shards": 1,
'number_of_replicas': 0, "number_of_replicas": 0,
# custom analyzer for analyzing file paths
# custom analyzer for analyzing file paths "analysis": {
'analysis': { "analyzer": {
'analyzer': { "file_path": {
'file_path': { "type": "custom",
'type': 'custom', "tokenizer": "path_hierarchy",
'tokenizer': 'path_hierarchy', "filter": ["lowercase"],
'filter': ['lowercase'] }
}
},
},
"mappings": {
"doc": {
"properties": {
"repository": {"type": "keyword"},
"author": user_mapping,
"authored_date": {"type": "date"},
"committer": user_mapping,
"committed_date": {"type": "date"},
"parent_shas": {"type": "keyword"},
"description": {"type": "text", "analyzer": "snowball"},
"files": {
"type": "text",
"analyzer": "file_path",
"fielddata": True,
},
}
} }
} },
}
},
'mappings': {
'doc': {
'properties': {
'repository': {'type': 'keyword'},
'author': user_mapping,
'authored_date': {'type': 'date'},
'committer': user_mapping,
'committed_date': {'type': 'date'},
'parent_shas': {'type': 'keyword'},
'description': {'type': 'text', 'analyzer': 'snowball'},
'files': {'type': 'text', 'analyzer': 'file_path', "fielddata": True}
}
}
}
} }
# create empty index # create empty index
try: try:
client.indices.create( client.indices.create(index=index, body=create_index_body)
index=index,
body=create_index_body,
)
except TransportError as e: except TransportError as e:
# ignore already existing index # ignore already existing index
if e.error == 'index_already_exists_exception': if e.error == "index_already_exists_exception":
pass pass
else: else:
raise raise
def parse_commits(head, name): def parse_commits(head, name):
""" """
Go through the git repository log and generate a document per commit Go through the git repository log and generate a document per commit
@@ -80,26 +77,24 @@ def parse_commits(head, name):
""" """
for commit in head.traverse(): for commit in head.traverse():
yield { yield {
'_id': commit.hexsha, "_id": commit.hexsha,
'repository': name, "repository": name,
'committed_date': datetime.fromtimestamp(commit.committed_date), "committed_date": datetime.fromtimestamp(commit.committed_date),
'committer': { "committer": {
'name': commit.committer.name, "name": commit.committer.name,
'email': commit.committer.email, "email": commit.committer.email,
}, },
'authored_date': datetime.fromtimestamp(commit.authored_date), "authored_date": datetime.fromtimestamp(commit.authored_date),
'author': { "author": {"name": commit.author.name, "email": commit.author.email},
'name': commit.author.name, "description": commit.message,
'email': commit.author.email, "parent_shas": [p.hexsha for p in commit.parents],
},
'description': commit.message,
'parent_shas': [p.hexsha for p in commit.parents],
# we only care about the filenames, not the per-file stats # we only care about the filenames, not the per-file stats
'files': list(commit.stats.files), "files": list(commit.stats.files),
'stats': commit.stats.total, "stats": commit.stats.total,
} }
def load_repo(client, path=None, index='git'):
def load_repo(client, path=None, index="git"):
""" """
Parse a git repository with all it's commits and load it into elasticsearch Parse a git repository with all it's commits and load it into elasticsearch
using `client`. If the index doesn't exist it will be created. using `client`. If the index doesn't exist it will be created.
@@ -114,18 +109,18 @@ def load_repo(client, path=None, index='git'):
# in - since the `parse_commits` function is a generator this will avoid # in - since the `parse_commits` function is a generator this will avoid
# loading all the commits into memory # loading all the commits into memory
for ok, result in streaming_bulk( for ok, result in streaming_bulk(
client, client,
parse_commits(repo.refs.master.commit, repo_name), parse_commits(repo.refs.master.commit, repo_name),
index=index, index=index,
doc_type='doc', doc_type="doc",
chunk_size=50 # keep the batch sizes small for appearances only chunk_size=50, # keep the batch sizes small for appearances only
): ):
action, result = result.popitem() action, result = result.popitem()
doc_id = '/%s/doc/%s' % (index, result['_id']) doc_id = "/%s/doc/%s" % (index, result["_id"])
# process the information from ES whether the document has been # process the information from ES whether the document has been
# successfully indexed # successfully indexed
if not ok: if not ok:
print('Failed to %s document %s: %r' % (action, doc_id, result)) print("Failed to %s document %s: %r" % (action, doc_id, result))
else: else:
print(doc_id) print(doc_id)
@@ -133,36 +128,40 @@ def load_repo(client, path=None, index='git'):
# we manually update some documents to add additional information # we manually update some documents to add additional information
UPDATES = [ UPDATES = [
{ {
'_type': 'doc', "_type": "doc",
'_id': '20fbba1230cabbc0f4644f917c6c2be52b8a63e8', "_id": "20fbba1230cabbc0f4644f917c6c2be52b8a63e8",
'_op_type': 'update', "_op_type": "update",
'doc': {'initial_commit': True} "doc": {"initial_commit": True},
}, },
{ {
'_type': 'doc', "_type": "doc",
'_id': 'ae0073c8ca7e24d237ffd56fba495ed409081bf4', "_id": "ae0073c8ca7e24d237ffd56fba495ed409081bf4",
'_op_type': 'update', "_op_type": "update",
'doc': {'release': '5.0.0'} "doc": {"release": "5.0.0"},
}, },
] ]
if __name__ == '__main__': if __name__ == "__main__":
# get trace logger and set level # get trace logger and set level
tracer = logging.getLogger('elasticsearch.trace') tracer = logging.getLogger("elasticsearch.trace")
tracer.setLevel(logging.INFO) tracer.setLevel(logging.INFO)
tracer.addHandler(logging.FileHandler('/tmp/es_trace.log')) tracer.addHandler(logging.FileHandler("/tmp/es_trace.log"))
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument( parser.add_argument(
"-H", "--host", "-H",
"--host",
action="store", action="store",
default="localhost:9200", default="localhost:9200",
help="The elasticsearch host you wish to connect to. (Default: localhost:9200)") help="The elasticsearch host you wish to connect to. (Default: localhost:9200)",
)
parser.add_argument( parser.add_argument(
"-p", "--path", "-p",
"--path",
action="store", action="store",
default=None, default=None,
help="Path to git repo. Commits used as data to load into Elasticsearch. (Default: None") help="Path to git repo. Commits used as data to load into Elasticsearch. (Default: None",
)
args = parser.parse_args() args = parser.parse_args()
@@ -173,15 +172,19 @@ if __name__ == '__main__':
load_repo(es, path=args.path) load_repo(es, path=args.path)
# run the bulk operations # run the bulk operations
success, _ = bulk(es, UPDATES, index='git') success, _ = bulk(es, UPDATES, index="git")
print('Performed %d actions' % success) print("Performed %d actions" % success)
# we can now make docs visible for searching # we can now make docs visible for searching
es.indices.refresh(index='git') es.indices.refresh(index="git")
# now we can retrieve the documents # now we can retrieve the documents
initial_commit = es.get(index='git', doc_type='doc', id='20fbba1230cabbc0f4644f917c6c2be52b8a63e8') initial_commit = es.get(
print('%s: %s' % (initial_commit['_id'], initial_commit['_source']['committed_date'])) index="git", doc_type="doc", id="20fbba1230cabbc0f4644f917c6c2be52b8a63e8"
)
print(
"%s: %s" % (initial_commit["_id"], initial_commit["_source"]["committed_date"])
)
# and now we can count the documents # and now we can count the documents
print(es.count(index='git')['count'], 'documents in index') print(es.count(index="git")["count"], "documents in index")
+66 -69
View File
@@ -6,95 +6,92 @@ from dateutil.parser import parse as parse_date
from elasticsearch import Elasticsearch from elasticsearch import Elasticsearch
def print_search_stats(results): def print_search_stats(results):
print('=' * 80) print("=" * 80)
print('Total %d found in %dms' % (results['hits']['total'], results['took'])) print("Total %d found in %dms" % (results["hits"]["total"], results["took"]))
print('-' * 80) print("-" * 80)
def print_hits(results): def print_hits(results):
" Simple utility function to print results of a search query. " " Simple utility function to print results of a search query. "
print_search_stats(results) print_search_stats(results)
for hit in results['hits']['hits']: for hit in results["hits"]["hits"]:
# get created date for a repo and fallback to authored_date for a commit # get created date for a repo and fallback to authored_date for a commit
created_at = parse_date(hit['_source'].get('created_at', hit['_source']['authored_date'])) created_at = parse_date(
print('/%s/%s/%s (%s): %s' % ( hit["_source"].get("created_at", hit["_source"]["authored_date"])
hit['_index'], hit['_type'], hit['_id'], )
created_at.strftime('%Y-%m-%d'), print(
hit['_source']['description'].split('\n')[0])) "/%s/%s/%s (%s): %s"
% (
hit["_index"],
hit["_type"],
hit["_id"],
created_at.strftime("%Y-%m-%d"),
hit["_source"]["description"].split("\n")[0],
)
)
print('=' * 80) print("=" * 80)
print() print()
# get trace logger and set level # get trace logger and set level
tracer = logging.getLogger('elasticsearch.trace') tracer = logging.getLogger("elasticsearch.trace")
tracer.setLevel(logging.INFO) tracer.setLevel(logging.INFO)
tracer.addHandler(logging.FileHandler('/tmp/es_trace.log')) tracer.addHandler(logging.FileHandler("/tmp/es_trace.log"))
# instantiate es client, connects to localhost:9200 by default # instantiate es client, connects to localhost:9200 by default
es = Elasticsearch() es = Elasticsearch()
print('Empty search:') print("Empty search:")
print_hits(es.search(index='git')) print_hits(es.search(index="git"))
print('Find commits that says "fix" without touching tests:') print('Find commits that says "fix" without touching tests:')
result = es.search( result = es.search(
index='git', index="git",
doc_type='doc', doc_type="doc",
body={ body={
'query': { "query": {
'bool': { "bool": {
'must': { "must": {"match": {"description": "fix"}},
'match': {'description': 'fix'} "must_not": {"term": {"files": "test_elasticsearch"}},
},
'must_not': {
'term': {'files': 'test_elasticsearch'}
}
}
}
}
)
print_hits(result)
print('Last 8 Commits for elasticsearch-py:')
result = es.search(
index='git',
doc_type='doc',
body={
'query': {
'term': {
'repository': 'elasticsearch-py'
}
},
'sort': [
{'committed_date': {'order': 'desc'}}
],
'size': 8
}
)
print_hits(result)
print('Stats for top 10 committers:')
result = es.search(
index='git',
doc_type='doc',
body={
'size': 0,
'aggs': {
'committers': {
'terms': {
'field': 'committer.name.keyword',
},
'aggs': {
'line_stats': {
'stats': {'field': 'stats.lines'}
} }
}
} }
} },
} )
print_hits(result)
print("Last 8 Commits for elasticsearch-py:")
result = es.search(
index="git",
doc_type="doc",
body={
"query": {"term": {"repository": "elasticsearch-py"}},
"sort": [{"committed_date": {"order": "desc"}}],
"size": 8,
},
)
print_hits(result)
print("Stats for top 10 committers:")
result = es.search(
index="git",
doc_type="doc",
body={
"size": 0,
"aggs": {
"committers": {
"terms": {"field": "committer.name.keyword"},
"aggs": {"line_stats": {"stats": {"field": "stats.lines"}}},
}
},
},
) )
print_search_stats(result) print_search_stats(result)
for committer in result['aggregations']['committers']['buckets']: for committer in result["aggregations"]["committers"]["buckets"]:
print('%15s: %3d commits changing %6d lines' % ( print(
committer['key'], committer['doc_count'], committer['line_stats']['sum'])) "%15s: %3d commits changing %6d lines"
print('=' * 80) % (committer["key"], committer["doc_count"], committer["line_stats"]["sum"])
)
print("=" * 80)
+5 -3
View File
@@ -1,4 +1,5 @@
from collections import defaultdict from collections import defaultdict
try: try:
# python 2.6 # python 2.6
from unittest2 import TestCase, SkipTest from unittest2 import TestCase, SkipTest
@@ -7,6 +8,7 @@ except ImportError:
from elasticsearch import Elasticsearch from elasticsearch import Elasticsearch
class DummyTransport(object): class DummyTransport(object):
def __init__(self, hosts, responses=None, **kwargs): def __init__(self, hosts, responses=None, **kwargs):
self.hosts = hosts self.hosts = hosts
@@ -46,7 +48,7 @@ class TestElasticsearchTestCase(ElasticsearchTestCase):
self.assert_call_count_equals(0) self.assert_call_count_equals(0)
def test_each_call_is_recorded(self): def test_each_call_is_recorded(self):
self.client.transport.perform_request('GET', '/') self.client.transport.perform_request("GET", "/")
self.client.transport.perform_request('DELETE', '/42', params={}, body='body') self.client.transport.perform_request("DELETE", "/42", params={}, body="body")
self.assert_call_count_equals(2) self.assert_call_count_equals(2)
self.assertEquals([({}, 'body')], self.assert_url_called('DELETE', '/42', 1)) self.assertEquals([({}, "body")], self.assert_url_called("DELETE", "/42", 1))
@@ -1,19 +1,20 @@
from test_elasticsearch.test_cases import ElasticsearchTestCase from test_elasticsearch.test_cases import ElasticsearchTestCase
class TestIndices(ElasticsearchTestCase): class TestIndices(ElasticsearchTestCase):
def test_create_one_index(self): def test_create_one_index(self):
self.client.indices.create('test-index') self.client.indices.create("test-index")
self.assert_url_called('PUT', '/test-index') self.assert_url_called("PUT", "/test-index")
def test_delete_multiple_indices(self): def test_delete_multiple_indices(self):
self.client.indices.delete(['test-index', 'second.index', 'third/index']) self.client.indices.delete(["test-index", "second.index", "third/index"])
self.assert_url_called('DELETE', '/test-index,second.index,third%2Findex') self.assert_url_called("DELETE", "/test-index,second.index,third%2Findex")
def test_exists_index(self): def test_exists_index(self):
self.client.indices.exists('second.index,third/index') self.client.indices.exists("second.index,third/index")
self.assert_url_called('HEAD', '/second.index,third%2Findex') self.assert_url_called("HEAD", "/second.index,third%2Findex")
def test_passing_empty_value_for_required_param_raises_exception(self): def test_passing_empty_value_for_required_param_raises_exception(self):
self.assertRaises(ValueError, self.client.indices.exists, index=None) self.assertRaises(ValueError, self.client.indices.exists, index=None)
self.assertRaises(ValueError, self.client.indices.exists, index=[]) self.assertRaises(ValueError, self.client.indices.exists, index=[])
self.assertRaises(ValueError, self.client.indices.exists, index='') self.assertRaises(ValueError, self.client.indices.exists, index="")
+14 -17
View File
@@ -6,35 +6,32 @@ from elasticsearch.compat import PY2
from ..test_cases import TestCase, SkipTest from ..test_cases import TestCase, SkipTest
class TestMakePath(TestCase): class TestMakePath(TestCase):
def test_handles_unicode(self): def test_handles_unicode(self):
id = "中文" id = "中文"
self.assertEquals('/some-index/type/%E4%B8%AD%E6%96%87', _make_path('some-index', 'type', id)) self.assertEquals(
"/some-index/type/%E4%B8%AD%E6%96%87", _make_path("some-index", "type", id)
)
def test_handles_utf_encoded_string(self): def test_handles_utf_encoded_string(self):
if not PY2: if not PY2:
raise SkipTest('Only relevant for py2') raise SkipTest("Only relevant for py2")
id = "中文".encode('utf-8') id = "中文".encode("utf-8")
self.assertEquals('/some-index/type/%E4%B8%AD%E6%96%87', _make_path('some-index', 'type', id)) self.assertEquals(
"/some-index/type/%E4%B8%AD%E6%96%87", _make_path("some-index", "type", id)
)
class TestEscape(TestCase): class TestEscape(TestCase):
def test_handles_ascii(self): def test_handles_ascii(self):
string = "abc123" string = "abc123"
self.assertEquals( self.assertEquals(b"abc123", _escape(string))
b'abc123',
_escape(string)
)
def test_handles_unicode(self): def test_handles_unicode(self):
string = "中文" string = "中文"
self.assertEquals( self.assertEquals(b"\xe4\xb8\xad\xe6\x96\x87", _escape(string))
b'\xe4\xb8\xad\xe6\x96\x87',
_escape(string)
)
def test_handles_bytestring(self): def test_handles_bytestring(self):
string = b'celery-task-meta-c4f1201f-eb7b-41d5-9318-a75a8cfbdaa0' string = b"celery-task-meta-c4f1201f-eb7b-41d5-9318-a75a8cfbdaa0"
self.assertEquals( self.assertEquals(string, _escape(string))
string,
_escape(string)
)
+167 -106
View File
@@ -6,9 +6,13 @@ import urllib3
import warnings import warnings
from requests.auth import AuthBase from requests.auth import AuthBase
from elasticsearch.exceptions import TransportError, ConflictError, RequestError, NotFoundError from elasticsearch.exceptions import (
from elasticsearch.connection import RequestsHttpConnection, \ TransportError,
Urllib3HttpConnection ConflictError,
RequestError,
NotFoundError,
)
from elasticsearch.connection import RequestsHttpConnection, Urllib3HttpConnection
from elasticsearch.exceptions import ImproperlyConfigured from elasticsearch.exceptions import ImproperlyConfigured
from .test_cases import TestCase, SkipTest from .test_cases import TestCase, SkipTest
@@ -22,20 +26,18 @@ class TestUrllib3Connection(TestCase):
# it means SSLContext is not available for that version of python # it means SSLContext is not available for that version of python
# and we should skip this test. # and we should skip this test.
raise SkipTest( raise SkipTest(
"Test test_ssl_context is skipped cause SSLContext is not available for this version of ptyhon") "Test test_ssl_context is skipped cause SSLContext is not available for this version of ptyhon"
)
con = Urllib3HttpConnection(use_ssl=True, ssl_context=context) con = Urllib3HttpConnection(use_ssl=True, ssl_context=context)
self.assertEqual(len(con.pool.conn_kw.keys()), 1) self.assertEqual(len(con.pool.conn_kw.keys()), 1)
self.assertIsInstance( self.assertIsInstance(con.pool.conn_kw["ssl_context"], ssl.SSLContext)
con.pool.conn_kw['ssl_context'],
ssl.SSLContext
)
self.assertTrue(con.use_ssl) self.assertTrue(con.use_ssl)
def test_http_compression(self): def test_http_compression(self):
con = Urllib3HttpConnection(http_compress=True) con = Urllib3HttpConnection(http_compress=True)
self.assertTrue(con.http_compress) self.assertTrue(con.http_compress)
self.assertEquals(con.headers['content-encoding'], 'gzip') self.assertEquals(con.headers["content-encoding"], "gzip")
def test_timeout_set(self): def test_timeout_set(self):
con = Urllib3HttpConnection(timeout=42) con = Urllib3HttpConnection(timeout=42)
@@ -43,40 +45,60 @@ class TestUrllib3Connection(TestCase):
def test_keep_alive_is_on_by_default(self): def test_keep_alive_is_on_by_default(self):
con = Urllib3HttpConnection() con = Urllib3HttpConnection()
self.assertEquals({'connection': 'keep-alive', self.assertEquals(
'content-type': 'application/json'}, con.headers) {"connection": "keep-alive", "content-type": "application/json"},
con.headers,
)
def test_http_auth(self): def test_http_auth(self):
con = Urllib3HttpConnection(http_auth='username:secret') con = Urllib3HttpConnection(http_auth="username:secret")
self.assertEquals({ self.assertEquals(
'authorization': 'Basic dXNlcm5hbWU6c2VjcmV0', {
'connection': 'keep-alive', "authorization": "Basic dXNlcm5hbWU6c2VjcmV0",
'content-type': 'application/json' "connection": "keep-alive",
}, con.headers) "content-type": "application/json",
},
con.headers,
)
def test_http_auth_tuple(self): def test_http_auth_tuple(self):
con = Urllib3HttpConnection(http_auth=('username', 'secret')) con = Urllib3HttpConnection(http_auth=("username", "secret"))
self.assertEquals({'authorization': 'Basic dXNlcm5hbWU6c2VjcmV0', self.assertEquals(
'content-type': 'application/json', {
'connection': 'keep-alive'}, con.headers) "authorization": "Basic dXNlcm5hbWU6c2VjcmV0",
"content-type": "application/json",
"connection": "keep-alive",
},
con.headers,
)
def test_http_auth_list(self): def test_http_auth_list(self):
con = Urllib3HttpConnection(http_auth=['username', 'secret']) con = Urllib3HttpConnection(http_auth=["username", "secret"])
self.assertEquals({'authorization': 'Basic dXNlcm5hbWU6c2VjcmV0', self.assertEquals(
'content-type': 'application/json', {
'connection': 'keep-alive'}, con.headers) "authorization": "Basic dXNlcm5hbWU6c2VjcmV0",
"content-type": "application/json",
"connection": "keep-alive",
},
con.headers,
)
def test_uses_https_if_verify_certs_is_off(self): def test_uses_https_if_verify_certs_is_off(self):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
con = Urllib3HttpConnection(use_ssl=True, verify_certs=False) con = Urllib3HttpConnection(use_ssl=True, verify_certs=False)
self.assertEquals(1, len(w)) self.assertEquals(1, len(w))
self.assertEquals('Connecting to localhost using SSL with verify_certs=False is insecure.', str(w[0].message)) self.assertEquals(
"Connecting to localhost using SSL with verify_certs=False is insecure.",
str(w[0].message),
)
self.assertIsInstance(con.pool, urllib3.HTTPSConnectionPool) self.assertIsInstance(con.pool, urllib3.HTTPSConnectionPool)
def nowarn_when_test_uses_https_if_verify_certs_is_off(self): def nowarn_when_test_uses_https_if_verify_certs_is_off(self):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
con = Urllib3HttpConnection(use_ssl=True, verify_certs=False, ssl_show_warn=False) con = Urllib3HttpConnection(
use_ssl=True, verify_certs=False, ssl_show_warn=False
)
self.assertEquals(0, len(w)) self.assertEquals(0, len(w))
self.assertIsInstance(con.pool, urllib3.HTTPSConnectionPool) self.assertIsInstance(con.pool, urllib3.HTTPSConnectionPool)
@@ -85,9 +107,13 @@ class TestUrllib3Connection(TestCase):
con = Urllib3HttpConnection() con = Urllib3HttpConnection()
self.assertIsInstance(con.pool, urllib3.HTTPConnectionPool) self.assertIsInstance(con.pool, urllib3.HTTPConnectionPool)
class TestRequestsConnection(TestCase): class TestRequestsConnection(TestCase):
def _get_mock_connection(self, connection_params={}, status_code=200, response_body='{}'): def _get_mock_connection(
self, connection_params={}, status_code=200, response_body="{}"
):
con = RequestsHttpConnection(**connection_params) con = RequestsHttpConnection(**connection_params)
def _dummy_send(*args, **kwargs): def _dummy_send(*args, **kwargs):
dummy_response = Mock() dummy_response = Mock()
dummy_response.headers = {} dummy_response.headers = {}
@@ -97,20 +123,21 @@ class TestRequestsConnection(TestCase):
dummy_response.cookies = {} dummy_response.cookies = {}
_dummy_send.call_args = (args, kwargs) _dummy_send.call_args = (args, kwargs)
return dummy_response return dummy_response
con.session.send = _dummy_send con.session.send = _dummy_send
return con return con
def _get_request(self, connection, *args, **kwargs): def _get_request(self, connection, *args, **kwargs):
if 'body' in kwargs: if "body" in kwargs:
kwargs['body'] = kwargs['body'].encode('utf-8') kwargs["body"] = kwargs["body"].encode("utf-8")
status, headers, data = connection.perform_request(*args, **kwargs) status, headers, data = connection.perform_request(*args, **kwargs)
self.assertEquals(200, status) self.assertEquals(200, status)
self.assertEquals('{}', data) self.assertEquals("{}", data)
timeout = kwargs.pop('timeout', connection.timeout) timeout = kwargs.pop("timeout", connection.timeout)
args, kwargs = connection.session.send.call_args args, kwargs = connection.session.send.call_args
self.assertEquals(timeout, kwargs['timeout']) self.assertEquals(timeout, kwargs["timeout"])
self.assertEquals(1, len(args)) self.assertEquals(1, len(args))
return args[0] return args[0]
@@ -126,73 +153,96 @@ class TestRequestsConnection(TestCase):
def test_uses_https_if_verify_certs_is_off(self): def test_uses_https_if_verify_certs_is_off(self):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
con = self._get_mock_connection({'use_ssl': True, 'url_prefix': 'url', 'verify_certs': False}) con = self._get_mock_connection(
{"use_ssl": True, "url_prefix": "url", "verify_certs": False}
)
self.assertEquals(1, len(w)) self.assertEquals(1, len(w))
self.assertEquals('Connecting to https://localhost:9200/url using SSL with verify_certs=False is insecure.', str(w[0].message)) self.assertEquals(
"Connecting to https://localhost:9200/url using SSL with verify_certs=False is insecure.",
str(w[0].message),
)
request = self._get_request(con, 'GET', '/') request = self._get_request(con, "GET", "/")
self.assertEquals('https://localhost:9200/url/', request.url) self.assertEquals("https://localhost:9200/url/", request.url)
self.assertEquals('GET', request.method) self.assertEquals("GET", request.method)
self.assertEquals(None, request.body) self.assertEquals(None, request.body)
def nowarn_when_test_uses_https_if_verify_certs_is_off(self): def nowarn_when_test_uses_https_if_verify_certs_is_off(self):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
con = self._get_mock_connection({'use_ssl': True, 'url_prefix': 'url', 'verify_certs': False, 'ssl_show_warn': False}) con = self._get_mock_connection(
{
"use_ssl": True,
"url_prefix": "url",
"verify_certs": False,
"ssl_show_warn": False,
}
)
self.assertEquals(0, len(w)) self.assertEquals(0, len(w))
request = self._get_request(con, 'GET', '/') request = self._get_request(con, "GET", "/")
self.assertEquals('https://localhost:9200/url/', request.url) self.assertEquals("https://localhost:9200/url/", request.url)
self.assertEquals('GET', request.method) self.assertEquals("GET", request.method)
self.assertEquals(None, request.body) self.assertEquals(None, request.body)
def test_merge_headers(self): def test_merge_headers(self):
con = self._get_mock_connection(connection_params={'headers': {'h1': 'v1', 'h2': 'v2'}}) con = self._get_mock_connection(
req = self._get_request(con, 'GET', '/', headers={'h2': 'v2p', 'h3': 'v3'}) connection_params={"headers": {"h1": "v1", "h2": "v2"}}
self.assertEquals(req.headers['h1'], 'v1') )
self.assertEquals(req.headers['h2'], 'v2p') req = self._get_request(con, "GET", "/", headers={"h2": "v2p", "h3": "v3"})
self.assertEquals(req.headers['h3'], 'v3') self.assertEquals(req.headers["h1"], "v1")
self.assertEquals(req.headers["h2"], "v2p")
self.assertEquals(req.headers["h3"], "v3")
def test_http_auth(self): def test_http_auth(self):
con = RequestsHttpConnection(http_auth='username:secret') con = RequestsHttpConnection(http_auth="username:secret")
self.assertEquals(('username', 'secret'), con.session.auth) self.assertEquals(("username", "secret"), con.session.auth)
def test_http_auth_tuple(self): def test_http_auth_tuple(self):
con = RequestsHttpConnection(http_auth=('username', 'secret')) con = RequestsHttpConnection(http_auth=("username", "secret"))
self.assertEquals(('username', 'secret'), con.session.auth) self.assertEquals(("username", "secret"), con.session.auth)
def test_http_auth_list(self): def test_http_auth_list(self):
con = RequestsHttpConnection(http_auth=['username', 'secret']) con = RequestsHttpConnection(http_auth=["username", "secret"])
self.assertEquals(('username', 'secret'), con.session.auth) self.assertEquals(("username", "secret"), con.session.auth)
def test_repr(self): def test_repr(self):
con = self._get_mock_connection({"host": "elasticsearch.com", "port": 443}) con = self._get_mock_connection({"host": "elasticsearch.com", "port": 443})
self.assertEquals('<RequestsHttpConnection: http://elasticsearch.com:443>', repr(con)) self.assertEquals(
"<RequestsHttpConnection: http://elasticsearch.com:443>", repr(con)
)
def test_conflict_error_is_returned_on_409(self): def test_conflict_error_is_returned_on_409(self):
con = self._get_mock_connection(status_code=409) con = self._get_mock_connection(status_code=409)
self.assertRaises(ConflictError, con.perform_request, 'GET', '/', {}, '') self.assertRaises(ConflictError, con.perform_request, "GET", "/", {}, "")
def test_not_found_error_is_returned_on_404(self): def test_not_found_error_is_returned_on_404(self):
con = self._get_mock_connection(status_code=404) con = self._get_mock_connection(status_code=404)
self.assertRaises(NotFoundError, con.perform_request, 'GET', '/', {}, '') self.assertRaises(NotFoundError, con.perform_request, "GET", "/", {}, "")
def test_request_error_is_returned_on_400(self): def test_request_error_is_returned_on_400(self):
con = self._get_mock_connection(status_code=400) con = self._get_mock_connection(status_code=400)
self.assertRaises(RequestError, con.perform_request, 'GET', '/', {}, '') self.assertRaises(RequestError, con.perform_request, "GET", "/", {}, "")
@patch('elasticsearch.connection.base.logger') @patch("elasticsearch.connection.base.logger")
def test_head_with_404_doesnt_get_logged(self, logger): def test_head_with_404_doesnt_get_logged(self, logger):
con = self._get_mock_connection(status_code=404) con = self._get_mock_connection(status_code=404)
self.assertRaises(NotFoundError, con.perform_request, 'HEAD', '/', {}, '') self.assertRaises(NotFoundError, con.perform_request, "HEAD", "/", {}, "")
self.assertEquals(0, logger.warning.call_count) self.assertEquals(0, logger.warning.call_count)
@patch('elasticsearch.connection.base.tracer') @patch("elasticsearch.connection.base.tracer")
@patch('elasticsearch.connection.base.logger') @patch("elasticsearch.connection.base.logger")
def test_failed_request_logs_and_traces(self, logger, tracer): def test_failed_request_logs_and_traces(self, logger, tracer):
con = self._get_mock_connection(response_body='{"answer": 42}', status_code=500) con = self._get_mock_connection(response_body='{"answer": 42}', status_code=500)
self.assertRaises(TransportError, con.perform_request, 'GET', '/', {'param': 42}, '{}'.encode('utf-8')) self.assertRaises(
TransportError,
con.perform_request,
"GET",
"/",
{"param": 42},
"{}".encode("utf-8"),
)
# trace request # trace request
self.assertEquals(1, tracer.info.call_count) self.assertEquals(1, tracer.info.call_count)
@@ -200,90 +250,101 @@ class TestRequestsConnection(TestCase):
self.assertEquals(1, tracer.debug.call_count) self.assertEquals(1, tracer.debug.call_count)
# log url and duration # log url and duration
self.assertEquals(1, logger.warning.call_count) self.assertEquals(1, logger.warning.call_count)
self.assertTrue(re.match( self.assertTrue(
'^GET http://localhost:9200/\?param=42 \[status:500 request:0.[0-9]{3}s\]', re.match(
logger.warning.call_args[0][0] % logger.warning.call_args[0][1:] "^GET http://localhost:9200/\?param=42 \[status:500 request:0.[0-9]{3}s\]",
)) logger.warning.call_args[0][0] % logger.warning.call_args[0][1:],
)
)
@patch('elasticsearch.connection.base.tracer') @patch("elasticsearch.connection.base.tracer")
@patch('elasticsearch.connection.base.logger') @patch("elasticsearch.connection.base.logger")
def test_success_logs_and_traces(self, logger, tracer): def test_success_logs_and_traces(self, logger, tracer):
con = self._get_mock_connection(response_body='''{"answer": "that's it!"}''') con = self._get_mock_connection(response_body="""{"answer": "that's it!"}""")
status, headers, data = con.perform_request('GET', '/', {'param': 42}, '''{"question": "what's that?"}'''.encode('utf-8')) status, headers, data = con.perform_request(
"GET",
"/",
{"param": 42},
"""{"question": "what's that?"}""".encode("utf-8"),
)
# trace request # trace request
self.assertEquals(1, tracer.info.call_count) self.assertEquals(1, tracer.info.call_count)
self.assertEquals( self.assertEquals(
"""curl -H 'Content-Type: application/json' -XGET 'http://localhost:9200/?pretty&param=42' -d '{\n "question": "what\\u0027s that?"\n}'""", """curl -H 'Content-Type: application/json' -XGET 'http://localhost:9200/?pretty&param=42' -d '{\n "question": "what\\u0027s that?"\n}'""",
tracer.info.call_args[0][0] % tracer.info.call_args[0][1:] tracer.info.call_args[0][0] % tracer.info.call_args[0][1:],
) )
# trace response # trace response
self.assertEquals(1, tracer.debug.call_count) self.assertEquals(1, tracer.debug.call_count)
self.assertTrue(re.match( self.assertTrue(
'#\[200\] \(0.[0-9]{3}s\)\n#\{\n# "answer": "that\\\\u0027s it!"\n#\}', re.match(
tracer.debug.call_args[0][0] % tracer.debug.call_args[0][1:] '#\[200\] \(0.[0-9]{3}s\)\n#\{\n# "answer": "that\\\\u0027s it!"\n#\}',
)) tracer.debug.call_args[0][0] % tracer.debug.call_args[0][1:],
)
)
# log url and duration # log url and duration
self.assertEquals(1, logger.info.call_count) self.assertEquals(1, logger.info.call_count)
self.assertTrue(re.match( self.assertTrue(
'GET http://localhost:9200/\?param=42 \[status:200 request:0.[0-9]{3}s\]', re.match(
logger.info.call_args[0][0] % logger.info.call_args[0][1:] "GET http://localhost:9200/\?param=42 \[status:200 request:0.[0-9]{3}s\]",
)) logger.info.call_args[0][0] % logger.info.call_args[0][1:],
)
)
# log request body and response # log request body and response
self.assertEquals(2, logger.debug.call_count) self.assertEquals(2, logger.debug.call_count)
req, resp = logger.debug.call_args_list req, resp = logger.debug.call_args_list
self.assertEquals( self.assertEquals('> {"question": "what\'s that?"}', req[0][0] % req[0][1:])
'> {"question": "what\'s that?"}', self.assertEquals('< {"answer": "that\'s it!"}', resp[0][0] % resp[0][1:])
req[0][0] % req[0][1:]
)
self.assertEquals(
'< {"answer": "that\'s it!"}',
resp[0][0] % resp[0][1:]
)
def test_defaults(self): def test_defaults(self):
con = self._get_mock_connection() con = self._get_mock_connection()
request = self._get_request(con, 'GET', '/') request = self._get_request(con, "GET", "/")
self.assertEquals('http://localhost:9200/', request.url) self.assertEquals("http://localhost:9200/", request.url)
self.assertEquals('GET', request.method) self.assertEquals("GET", request.method)
self.assertEquals(None, request.body) self.assertEquals(None, request.body)
def test_params_properly_encoded(self): def test_params_properly_encoded(self):
con = self._get_mock_connection() con = self._get_mock_connection()
request = self._get_request(con, 'GET', '/', params={'param': 'value with spaces'}) request = self._get_request(
con, "GET", "/", params={"param": "value with spaces"}
)
self.assertEquals('http://localhost:9200/?param=value+with+spaces', request.url) self.assertEquals("http://localhost:9200/?param=value+with+spaces", request.url)
self.assertEquals('GET', request.method) self.assertEquals("GET", request.method)
self.assertEquals(None, request.body) self.assertEquals(None, request.body)
def test_body_attached(self): def test_body_attached(self):
con = self._get_mock_connection() con = self._get_mock_connection()
request = self._get_request(con, 'GET', '/', body='{"answer": 42}') request = self._get_request(con, "GET", "/", body='{"answer": 42}')
self.assertEquals('http://localhost:9200/', request.url) self.assertEquals("http://localhost:9200/", request.url)
self.assertEquals('GET', request.method) self.assertEquals("GET", request.method)
self.assertEquals('{"answer": 42}'.encode('utf-8'), request.body) self.assertEquals('{"answer": 42}'.encode("utf-8"), request.body)
def test_http_auth_attached(self): def test_http_auth_attached(self):
con = self._get_mock_connection({'http_auth': 'username:secret'}) con = self._get_mock_connection({"http_auth": "username:secret"})
request = self._get_request(con, 'GET', '/') request = self._get_request(con, "GET", "/")
self.assertEquals(request.headers['authorization'], 'Basic dXNlcm5hbWU6c2VjcmV0') self.assertEquals(
request.headers["authorization"], "Basic dXNlcm5hbWU6c2VjcmV0"
)
@patch('elasticsearch.connection.base.tracer') @patch("elasticsearch.connection.base.tracer")
def test_url_prefix(self, tracer): def test_url_prefix(self, tracer):
con = self._get_mock_connection({"url_prefix": "/some-prefix/"}) con = self._get_mock_connection({"url_prefix": "/some-prefix/"})
request = self._get_request(con, 'GET', '/_search', body='{"answer": 42}', timeout=0.1) request = self._get_request(
con, "GET", "/_search", body='{"answer": 42}', timeout=0.1
)
self.assertEquals('http://localhost:9200/some-prefix/_search', request.url) self.assertEquals("http://localhost:9200/some-prefix/_search", request.url)
self.assertEquals('GET', request.method) self.assertEquals("GET", request.method)
self.assertEquals('{"answer": 42}'.encode('utf-8'), request.body) self.assertEquals('{"answer": 42}'.encode("utf-8"), request.body)
# trace request # trace request
self.assertEquals(1, tracer.info.call_count) self.assertEquals(1, tracer.info.call_count)
self.assertEquals( self.assertEquals(
"curl -H 'Content-Type: application/json' -XGET 'http://localhost:9200/_search?pretty' -d '{\n \"answer\": 42\n}'", "curl -H 'Content-Type: application/json' -XGET 'http://localhost:9200/_search?pretty' -d '{\n \"answer\": 42\n}'",
tracer.info.call_args[0][0] % tracer.info.call_args[0][1:] tracer.info.call_args[0][0] % tracer.info.call_args[0][1:],
) )
+29 -13
View File
@@ -1,14 +1,21 @@
import time import time
from elasticsearch.connection_pool import ConnectionPool, RoundRobinSelector, DummyConnectionPool from elasticsearch.connection_pool import (
ConnectionPool,
RoundRobinSelector,
DummyConnectionPool,
)
from elasticsearch.exceptions import ImproperlyConfigured from elasticsearch.exceptions import ImproperlyConfigured
from .test_cases import TestCase from .test_cases import TestCase
class TestConnectionPool(TestCase): class TestConnectionPool(TestCase):
def test_dummy_cp_raises_exception_on_more_connections(self): def test_dummy_cp_raises_exception_on_more_connections(self):
self.assertRaises(ImproperlyConfigured, DummyConnectionPool, []) self.assertRaises(ImproperlyConfigured, DummyConnectionPool, [])
self.assertRaises(ImproperlyConfigured, DummyConnectionPool, [object(), object()]) self.assertRaises(
ImproperlyConfigured, DummyConnectionPool, [object(), object()]
)
def test_raises_exception_when_no_connections_defined(self): def test_raises_exception_when_no_connections_defined(self):
self.assertRaises(ImproperlyConfigured, ConnectionPool, []) self.assertRaises(ImproperlyConfigured, ConnectionPool, [])
@@ -32,13 +39,20 @@ class TestConnectionPool(TestCase):
def test_selectors_have_access_to_connection_opts(self): def test_selectors_have_access_to_connection_opts(self):
class MySelector(RoundRobinSelector): class MySelector(RoundRobinSelector):
def select(self, connections): def select(self, connections):
return self.connection_opts[super(MySelector, self).select(connections)]["actual"] return self.connection_opts[
pool = ConnectionPool([(x, {"actual": x*x}) for x in range(100)], selector_class=MySelector, randomize_hosts=False) super(MySelector, self).select(connections)
]["actual"]
pool = ConnectionPool(
[(x, {"actual": x * x}) for x in range(100)],
selector_class=MySelector,
randomize_hosts=False,
)
connections = [] connections = []
for _ in range(100): for _ in range(100):
connections.append(pool.get_connection()) connections.append(pool.get_connection())
self.assertEquals(connections, [x*x for x in range(100)]) self.assertEquals(connections, [x * x for x in range(100)])
def test_dead_nodes_are_removed_from_active_connections(self): def test_dead_nodes_are_removed_from_active_connections(self):
pool = ConnectionPool([(x, {}) for x in range(100)]) pool = ConnectionPool([(x, {}) for x in range(100)])
@@ -53,23 +67,26 @@ class TestConnectionPool(TestCase):
pool = ConnectionPool([(x, {}) for x in range(2)]) pool = ConnectionPool([(x, {}) for x in range(2)])
pool.mark_dead(0) pool.mark_dead(0)
self.assertEquals([1, 1, 1], [pool.get_connection(), pool.get_connection(), pool.get_connection(), ]) self.assertEquals(
[1, 1, 1],
[pool.get_connection(), pool.get_connection(), pool.get_connection()],
)
def test_connection_is_forcibly_resurrected_when_no_live_ones_are_availible(self): def test_connection_is_forcibly_resurrected_when_no_live_ones_are_availible(self):
pool = ConnectionPool([(x, {}) for x in range(2)]) pool = ConnectionPool([(x, {}) for x in range(2)])
pool.dead_count[0] = 1 pool.dead_count[0] = 1
pool.mark_dead(0) # failed twice, longer timeout pool.mark_dead(0) # failed twice, longer timeout
pool.mark_dead(1) # failed the first time, first to be resurrected pool.mark_dead(1) # failed the first time, first to be resurrected
self.assertEquals([], pool.connections) self.assertEquals([], pool.connections)
self.assertEquals(1, pool.get_connection()) self.assertEquals(1, pool.get_connection())
self.assertEquals([1,], pool.connections) self.assertEquals([1], pool.connections)
def test_connection_is_resurrected_after_its_timeout(self): def test_connection_is_resurrected_after_its_timeout(self):
pool = ConnectionPool([(x, {}) for x in range(100)]) pool = ConnectionPool([(x, {}) for x in range(100)])
now = time.time() now = time.time()
pool.mark_dead(42, now=now-61) pool.mark_dead(42, now=now - 61)
pool.get_connection() pool.get_connection()
self.assertEquals(42, pool.connections[-1]) self.assertEquals(42, pool.connections[-1])
self.assertEquals(100, len(pool.connections)) self.assertEquals(100, len(pool.connections))
@@ -89,7 +106,7 @@ class TestConnectionPool(TestCase):
pool.mark_dead(42, now=now) pool.mark_dead(42, now=now)
self.assertEquals(3, pool.dead_count[42]) self.assertEquals(3, pool.dead_count[42])
self.assertEquals((now + 4*60, 42), pool.dead.get()) self.assertEquals((now + 4 * 60, 42), pool.dead.get())
def test_timeout_for_failed_connections_is_limitted(self): def test_timeout_for_failed_connections_is_limitted(self):
pool = ConnectionPool([(x, {}) for x in range(100)]) pool = ConnectionPool([(x, {}) for x in range(100)])
@@ -98,7 +115,7 @@ class TestConnectionPool(TestCase):
pool.mark_dead(42, now=now) pool.mark_dead(42, now=now)
self.assertEquals(246, pool.dead_count[42]) self.assertEquals(246, pool.dead_count[42])
self.assertEquals((now + 32*60, 42), pool.dead.get()) self.assertEquals((now + 32 * 60, 42), pool.dead.get())
def test_dead_count_is_wiped_clean_for_connection_if_marked_live(self): def test_dead_count_is_wiped_clean_for_connection_if_marked_live(self):
pool = ConnectionPool([(x, {}) for x in range(100)]) pool = ConnectionPool([(x, {}) for x in range(100)])
@@ -109,4 +126,3 @@ class TestConnectionPool(TestCase):
self.assertEquals(3, pool.dead_count[42]) self.assertEquals(3, pool.dead_count[42])
pool.mark_live(42) pool.mark_live(42)
self.assertNotIn(42, pool.dead_count) self.assertNotIn(42, pool.dead_count)
+15 -12
View File
@@ -5,19 +5,22 @@ from .test_cases import TestCase
class TestTransformError(TestCase): class TestTransformError(TestCase):
def test_transform_error_parse_with_error_reason(self): def test_transform_error_parse_with_error_reason(self):
e = TransportError(500, 'InternalServerError', { e = TransportError(
'error': { 500,
'root_cause': [ "InternalServerError",
{"type": "error", "reason": "error reason"} {"error": {"root_cause": [{"type": "error", "reason": "error reason"}]}},
] )
}
})
self.assertEqual(str(e), "TransportError(500, 'InternalServerError', 'error reason')") self.assertEqual(
str(e), "TransportError(500, 'InternalServerError', 'error reason')"
)
def test_transform_error_parse_with_error_string(self): def test_transform_error_parse_with_error_string(self):
e = TransportError(500, 'InternalServerError', { e = TransportError(
'error': 'something error message' 500, "InternalServerError", {"error": "something error message"}
}) )
self.assertEqual(str(e), "TransportError(500, 'InternalServerError', 'something error message')") self.assertEqual(
str(e),
"TransportError(500, 'InternalServerError', 'something error message')",
)
+31 -10
View File
@@ -5,30 +5,44 @@ import uuid
from datetime import datetime from datetime import datetime
from decimal import Decimal from decimal import Decimal
from elasticsearch.serializer import JSONSerializer, Deserializer, DEFAULT_SERIALIZERS, TextSerializer from elasticsearch.serializer import (
JSONSerializer,
Deserializer,
DEFAULT_SERIALIZERS,
TextSerializer,
)
from elasticsearch.exceptions import SerializationError, ImproperlyConfigured from elasticsearch.exceptions import SerializationError, ImproperlyConfigured
from .test_cases import TestCase, SkipTest from .test_cases import TestCase, SkipTest
class TestJSONSerializer(TestCase): class TestJSONSerializer(TestCase):
def test_datetime_serialization(self): def test_datetime_serialization(self):
self.assertEquals('{"d":"2010-10-01T02:30:00"}', JSONSerializer().dumps({'d': datetime(2010, 10, 1, 2, 30)})) self.assertEquals(
'{"d":"2010-10-01T02:30:00"}',
JSONSerializer().dumps({"d": datetime(2010, 10, 1, 2, 30)}),
)
def test_decimal_serialization(self): def test_decimal_serialization(self):
if sys.version_info[:2] == (2, 6): if sys.version_info[:2] == (2, 6):
raise SkipTest("Float rounding is broken in 2.6.") raise SkipTest("Float rounding is broken in 2.6.")
self.assertEquals('{"d":3.8}', JSONSerializer().dumps({'d': Decimal('3.8')})) self.assertEquals('{"d":3.8}', JSONSerializer().dumps({"d": Decimal("3.8")}))
def test_uuid_serialization(self): def test_uuid_serialization(self):
self.assertEquals('{"d":"00000000-0000-0000-0000-000000000003"}', JSONSerializer().dumps({'d': uuid.UUID('00000000-0000-0000-0000-000000000003')})) self.assertEquals(
'{"d":"00000000-0000-0000-0000-000000000003"}',
JSONSerializer().dumps(
{"d": uuid.UUID("00000000-0000-0000-0000-000000000003")}
),
)
def test_raises_serialization_error_on_dump_error(self): def test_raises_serialization_error_on_dump_error(self):
self.assertRaises(SerializationError, JSONSerializer().dumps, object()) self.assertRaises(SerializationError, JSONSerializer().dumps, object())
def test_raises_serialization_error_on_load_error(self): def test_raises_serialization_error_on_load_error(self):
self.assertRaises(SerializationError, JSONSerializer().loads, object()) self.assertRaises(SerializationError, JSONSerializer().loads, object())
self.assertRaises(SerializationError, JSONSerializer().loads, '') self.assertRaises(SerializationError, JSONSerializer().loads, "")
self.assertRaises(SerializationError, JSONSerializer().loads, '{{') self.assertRaises(SerializationError, JSONSerializer().loads, "{{")
def test_strings_are_left_untouched(self): def test_strings_are_left_untouched(self):
self.assertEquals("你好", JSONSerializer().dumps("你好")) self.assertEquals("你好", JSONSerializer().dumps("你好"))
@@ -51,11 +65,18 @@ class TestDeserializer(TestCase):
self.assertEquals({"some": "data"}, self.de.loads('{"some":"data"}')) self.assertEquals({"some": "data"}, self.de.loads('{"some":"data"}'))
def test_deserializes_text_with_correct_ct(self): def test_deserializes_text_with_correct_ct(self):
self.assertEquals('{"some":"data"}', self.de.loads('{"some":"data"}', 'text/plain')) self.assertEquals(
self.assertEquals('{"some":"data"}', self.de.loads('{"some":"data"}', 'text/plain; charset=whatever')) '{"some":"data"}', self.de.loads('{"some":"data"}', "text/plain")
)
self.assertEquals(
'{"some":"data"}',
self.de.loads('{"some":"data"}', "text/plain; charset=whatever"),
)
def test_raises_serialization_error_on_unknown_mimetype(self): def test_raises_serialization_error_on_unknown_mimetype(self):
self.assertRaises(SerializationError, self.de.loads, '{}', 'text/html') self.assertRaises(SerializationError, self.de.loads, "{}", "text/html")
def test_raises_improperly_configured_when_default_mimetype_cannot_be_deserialized(self): def test_raises_improperly_configured_when_default_mimetype_cannot_be_deserialized(
self
):
self.assertRaises(ImproperlyConfigured, Deserializer, {}) self.assertRaises(ImproperlyConfigured, Deserializer, {})
+7 -1
View File
@@ -1,7 +1,11 @@
from elasticsearch.helpers.test import get_test_client, ElasticsearchTestCase as BaseTestCase from elasticsearch.helpers.test import (
get_test_client,
ElasticsearchTestCase as BaseTestCase,
)
client = None client = None
def get_client(**kwargs): def get_client(**kwargs):
global client global client
if client is not None and not kwargs: if client is not None and not kwargs:
@@ -10,6 +14,7 @@ def get_client(**kwargs):
# try and locate manual override in the local environment # try and locate manual override in the local environment
try: try:
from test_elasticsearch.local import get_client as local_get_client from test_elasticsearch.local import get_client as local_get_client
new_client = local_get_client(**kwargs) new_client = local_get_client(**kwargs)
except ImportError: except ImportError:
# fallback to using vanilla client # fallback to using vanilla client
@@ -24,6 +29,7 @@ def get_client(**kwargs):
def setup(): def setup():
get_client() get_client()
class ElasticsearchTestCase(BaseTestCase): class ElasticsearchTestCase(BaseTestCase):
@staticmethod @staticmethod
def _get_client(**kwargs): def _get_client(**kwargs):
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
from . import ElasticsearchTestCase from . import ElasticsearchTestCase
class TestUnicode(ElasticsearchTestCase): class TestUnicode(ElasticsearchTestCase):
def test_indices_analyze(self): def test_indices_analyze(self):
self.client.indices.analyze(body='{"text": "привет"}') self.client.indices.analyze(body='{"text": "привет"}')
+81 -59
View File
@@ -310,20 +310,20 @@ class TestBulk(ElasticsearchTestCase):
class TestScan(ElasticsearchTestCase): class TestScan(ElasticsearchTestCase):
mock_scroll_responses = [ mock_scroll_responses = [
{ {
'_scroll_id': 'dummy_id', "_scroll_id": "dummy_id",
'_shards': {'successful': 4, 'total': 5}, "_shards": {"successful": 4, "total": 5},
'hits': {'hits': [{'scroll_data': 42}]}, "hits": {"hits": [{"scroll_data": 42}]},
}, },
{ {
'_scroll_id': 'dummy_id', "_scroll_id": "dummy_id",
'_shards': {'successful': 4, 'total': 5}, "_shards": {"successful": 4, "total": 5},
'hits': {'hits': []}, "hits": {"hits": []},
}, },
] ]
@classmethod @classmethod
def tearDownClass(cls): def tearDownClass(cls):
cls.client.transport.perform_request('DELETE', '/_search/scroll/_all') cls.client.transport.perform_request("DELETE", "/_search/scroll/_all")
super(TestScan, cls).tearDownClass() super(TestScan, cls).tearDownClass()
def test_order_can_be_preserved(self): def test_order_can_be_preserved(self):
@@ -366,87 +366,101 @@ class TestScan(ElasticsearchTestCase):
bulk.append({"value": x}) bulk.append({"value": x})
self.client.bulk(bulk, refresh=True) self.client.bulk(bulk, refresh=True)
with patch.object(self.client, 'scroll') as scroll_mock: with patch.object(self.client, "scroll") as scroll_mock:
scroll_mock.side_effect = self.mock_scroll_responses scroll_mock.side_effect = self.mock_scroll_responses
data = list(helpers.scan( data = list(
self.client, helpers.scan(
index='test_index', self.client,
size=2, index="test_index",
raise_on_error=False, size=2,
clear_scroll=False raise_on_error=False,
)) clear_scroll=False,
)
)
self.assertEqual(len(data), 3) self.assertEqual(len(data), 3)
self.assertEqual(data[-1], {'scroll_data': 42}) self.assertEqual(data[-1], {"scroll_data": 42})
scroll_mock.side_effect = self.mock_scroll_responses scroll_mock.side_effect = self.mock_scroll_responses
with self.assertRaises(ScanError): with self.assertRaises(ScanError):
data = list(helpers.scan( data = list(
self.client, helpers.scan(
index='test_index', self.client,
size=2, index="test_index",
raise_on_error=True, size=2,
clear_scroll=False raise_on_error=True,
)) clear_scroll=False,
)
)
self.assertEqual(len(data), 3) self.assertEqual(len(data), 3)
self.assertEqual(data[-1], {'scroll_data': 42}) self.assertEqual(data[-1], {"scroll_data": 42})
def test_initial_search_error(self): def test_initial_search_error(self):
with patch.object(self, 'client') as client_mock: with patch.object(self, "client") as client_mock:
client_mock.search.return_value = { client_mock.search.return_value = {
'_scroll_id': 'dummy_id', "_scroll_id": "dummy_id",
'_shards': {'successful': 4, 'total': 5}, "_shards": {"successful": 4, "total": 5},
'hits': {'hits': [{'search_data': 1}]}, "hits": {"hits": [{"search_data": 1}]},
} }
client_mock.scroll.side_effect = self.mock_scroll_responses client_mock.scroll.side_effect = self.mock_scroll_responses
data = list(helpers.scan(self.client, index='test_index', size=2, raise_on_error=False)) data = list(
self.assertEqual(data, [{'search_data': 1}, {'scroll_data': 42}]) helpers.scan(
self.client, index="test_index", size=2, raise_on_error=False
)
)
self.assertEqual(data, [{"search_data": 1}, {"scroll_data": 42}])
client_mock.scroll.side_effect = self.mock_scroll_responses client_mock.scroll.side_effect = self.mock_scroll_responses
with self.assertRaises(ScanError): with self.assertRaises(ScanError):
data = list( data = list(
helpers.scan(self.client, index='test_index', size=2, raise_on_error=True) helpers.scan(
self.client, index="test_index", size=2, raise_on_error=True
)
) )
self.assertEqual(data, [{'search_data': 1}]) self.assertEqual(data, [{"search_data": 1}])
client_mock.scroll.assert_not_called() client_mock.scroll.assert_not_called()
def test_no_scroll_id_fast_route(self): def test_no_scroll_id_fast_route(self):
with patch.object(self, 'client') as client_mock: with patch.object(self, "client") as client_mock:
client_mock.search.return_value = {'no': '_scroll_id'} client_mock.search.return_value = {"no": "_scroll_id"}
data = list(helpers.scan(self.client, index='test_index')) data = list(helpers.scan(self.client, index="test_index"))
self.assertEqual(data, []) self.assertEqual(data, [])
client_mock.scroll.assert_not_called() client_mock.scroll.assert_not_called()
client_mock.clear_scroll.assert_not_called() client_mock.clear_scroll.assert_not_called()
@patch('elasticsearch.helpers.actions.logger') @patch("elasticsearch.helpers.actions.logger")
def test_logger(self, logger_mock): def test_logger(self, logger_mock):
bulk = [] bulk = []
for x in range(4): for x in range(4):
bulk.append({'index': {'_index': 'test_index', '_type': '_doc'}}) bulk.append({"index": {"_index": "test_index", "_type": "_doc"}})
bulk.append({'value': x}) bulk.append({"value": x})
self.client.bulk(bulk, refresh=True) self.client.bulk(bulk, refresh=True)
with patch.object(self.client, 'scroll') as scroll_mock: with patch.object(self.client, "scroll") as scroll_mock:
scroll_mock.side_effect = self.mock_scroll_responses scroll_mock.side_effect = self.mock_scroll_responses
list(helpers.scan( list(
self.client, helpers.scan(
index='test_index', self.client,
size=2, index="test_index",
raise_on_error=False, size=2,
clear_scroll=False raise_on_error=False,
)) clear_scroll=False,
)
)
logger_mock.warning.assert_called() logger_mock.warning.assert_called()
scroll_mock.side_effect = self.mock_scroll_responses scroll_mock.side_effect = self.mock_scroll_responses
try: try:
list(helpers.scan( list(
self.client, helpers.scan(
index='test_index', self.client,
size=2, index="test_index",
raise_on_error=True, size=2,
clear_scroll=False raise_on_error=True,
)) clear_scroll=False,
)
)
except ScanError: except ScanError:
pass pass
logger_mock.warning.assert_called() logger_mock.warning.assert_called()
@@ -454,20 +468,28 @@ class TestScan(ElasticsearchTestCase):
def test_clear_scroll(self): def test_clear_scroll(self):
bulk = [] bulk = []
for x in range(4): for x in range(4):
bulk.append({'index': {'_index': 'test_index', '_type': '_doc'}}) bulk.append({"index": {"_index": "test_index", "_type": "_doc"}})
bulk.append({'value': x}) bulk.append({"value": x})
self.client.bulk(bulk, refresh=True) self.client.bulk(bulk, refresh=True)
with patch.object(self.client, 'clear_scroll', wraps=self.client.clear_scroll) as spy: with patch.object(
list(helpers.scan(self.client, index='test_index', size=2)) self.client, "clear_scroll", wraps=self.client.clear_scroll
) as spy:
list(helpers.scan(self.client, index="test_index", size=2))
spy.assert_called_once() spy.assert_called_once()
spy.reset_mock() spy.reset_mock()
list(helpers.scan(self.client, index='test_index', size=2, clear_scroll=True)) list(
helpers.scan(self.client, index="test_index", size=2, clear_scroll=True)
)
spy.assert_called_once() spy.assert_called_once()
spy.reset_mock() spy.reset_mock()
list(helpers.scan(self.client, index='test_index', size=2, clear_scroll=False)) list(
helpers.scan(
self.client, index="test_index", size=2, clear_scroll=False
)
)
spy.assert_not_called() spy.assert_not_called()
+116 -58
View File
@@ -9,11 +9,12 @@ from elasticsearch.exceptions import ConnectionError, ImproperlyConfigured
from .test_cases import TestCase from .test_cases import TestCase
class DummyConnection(Connection): class DummyConnection(Connection):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.exception = kwargs.pop('exception', None) self.exception = kwargs.pop("exception", None)
self.status, self.data = kwargs.pop('status', 200), kwargs.pop('data', '{}') self.status, self.data = kwargs.pop("status", 200), kwargs.pop("data", "{}")
self.headers = kwargs.pop('headers', {}) self.headers = kwargs.pop("headers", {})
self.calls = [] self.calls = []
super(DummyConnection, self).__init__(**kwargs) super(DummyConnection, self).__init__(**kwargs)
@@ -23,7 +24,8 @@ class DummyConnection(Connection):
raise self.exception raise self.exception
return self.status, self.headers, self.data return self.status, self.headers, self.data
CLUSTER_NODES = '''{
CLUSTER_NODES = """{
"_nodes" : { "_nodes" : {
"total" : 1, "total" : 1,
"successful" : 1, "successful" : 1,
@@ -46,18 +48,23 @@ CLUSTER_NODES = '''{
} }
} }
} }
}''' }"""
class TestHostsInfoCallback(TestCase): class TestHostsInfoCallback(TestCase):
def test_master_only_nodes_are_ignored(self): def test_master_only_nodes_are_ignored(self):
nodes = [ nodes = [
{'roles': [ "master"]}, {"roles": ["master"]},
{'roles': [ "master", "data", "ingest"]}, {"roles": ["master", "data", "ingest"]},
{'roles': [ "data", "ingest"]}, {"roles": ["data", "ingest"]},
{'roles': [ ]}, {"roles": []},
{} {},
]
chosen = [
i
for i, node_info in enumerate(nodes)
if get_host_info(node_info, i) is not None
] ]
chosen = [i for i, node_info in enumerate(nodes) if get_host_info(node_info, i) is not None]
self.assertEquals([1, 2, 3, 4], chosen) self.assertEquals([1, 2, 3, 4], chosen)
@@ -65,57 +72,70 @@ class TestTransport(TestCase):
def test_single_connection_uses_dummy_connection_pool(self): def test_single_connection_uses_dummy_connection_pool(self):
t = Transport([{}]) t = Transport([{}])
self.assertIsInstance(t.connection_pool, DummyConnectionPool) self.assertIsInstance(t.connection_pool, DummyConnectionPool)
t = Transport([{'host': 'localhost'}]) t = Transport([{"host": "localhost"}])
self.assertIsInstance(t.connection_pool, DummyConnectionPool) self.assertIsInstance(t.connection_pool, DummyConnectionPool)
def test_request_timeout_extracted_from_params_and_passed(self): def test_request_timeout_extracted_from_params_and_passed(self):
t = Transport([{}], connection_class=DummyConnection) t = Transport([{}], connection_class=DummyConnection)
t.perform_request('GET', '/', params={'request_timeout': 42}) t.perform_request("GET", "/", params={"request_timeout": 42})
self.assertEquals(1, len(t.get_connection().calls)) self.assertEquals(1, len(t.get_connection().calls))
self.assertEquals(('GET', '/', {}, None), t.get_connection().calls[0][0]) self.assertEquals(("GET", "/", {}, None), t.get_connection().calls[0][0])
self.assertEquals({'timeout': 42, 'ignore': (), 'headers': None}, t.get_connection().calls[0][1]) self.assertEquals(
{"timeout": 42, "ignore": (), "headers": None},
t.get_connection().calls[0][1],
)
def test_send_get_body_as_source(self): def test_send_get_body_as_source(self):
t = Transport([{}], send_get_body_as='source', connection_class=DummyConnection) t = Transport([{}], send_get_body_as="source", connection_class=DummyConnection)
t.perform_request('GET', '/', body={}) t.perform_request("GET", "/", body={})
self.assertEquals(1, len(t.get_connection().calls)) self.assertEquals(1, len(t.get_connection().calls))
self.assertEquals(('GET', '/', {'source': '{}'}, None), t.get_connection().calls[0][0]) self.assertEquals(
("GET", "/", {"source": "{}"}, None), t.get_connection().calls[0][0]
)
def test_send_get_body_as_post(self): def test_send_get_body_as_post(self):
t = Transport([{}], send_get_body_as='POST', connection_class=DummyConnection) t = Transport([{}], send_get_body_as="POST", connection_class=DummyConnection)
t.perform_request('GET', '/', body={}) t.perform_request("GET", "/", body={})
self.assertEquals(1, len(t.get_connection().calls)) self.assertEquals(1, len(t.get_connection().calls))
self.assertEquals(('POST', '/', None, b'{}'), t.get_connection().calls[0][0]) self.assertEquals(("POST", "/", None, b"{}"), t.get_connection().calls[0][0])
def test_body_gets_encoded_into_bytes(self): def test_body_gets_encoded_into_bytes(self):
t = Transport([{}], connection_class=DummyConnection) t = Transport([{}], connection_class=DummyConnection)
t.perform_request('GET', '/', body='你好') t.perform_request("GET", "/", body="你好")
self.assertEquals(1, len(t.get_connection().calls)) self.assertEquals(1, len(t.get_connection().calls))
self.assertEquals(('GET', '/', None, b'\xe4\xbd\xa0\xe5\xa5\xbd'), t.get_connection().calls[0][0]) self.assertEquals(
("GET", "/", None, b"\xe4\xbd\xa0\xe5\xa5\xbd"),
t.get_connection().calls[0][0],
)
def test_body_bytes_get_passed_untouched(self): def test_body_bytes_get_passed_untouched(self):
t = Transport([{}], connection_class=DummyConnection) t = Transport([{}], connection_class=DummyConnection)
body = b'\xe4\xbd\xa0\xe5\xa5\xbd' body = b"\xe4\xbd\xa0\xe5\xa5\xbd"
t.perform_request('GET', '/', body=body) t.perform_request("GET", "/", body=body)
self.assertEquals(1, len(t.get_connection().calls)) self.assertEquals(1, len(t.get_connection().calls))
self.assertEquals(('GET', '/', None, body), t.get_connection().calls[0][0]) self.assertEquals(("GET", "/", None, body), t.get_connection().calls[0][0])
def test_body_surrogates_replaced_encoded_into_bytes(self): def test_body_surrogates_replaced_encoded_into_bytes(self):
t = Transport([{}], connection_class=DummyConnection) t = Transport([{}], connection_class=DummyConnection)
t.perform_request('GET', '/', body='你好\uda6a') t.perform_request("GET", "/", body="你好\uda6a")
self.assertEquals(1, len(t.get_connection().calls)) self.assertEquals(1, len(t.get_connection().calls))
self.assertEquals(('GET', '/', None, b'\xe4\xbd\xa0\xe5\xa5\xbd\xed\xa9\xaa'), t.get_connection().calls[0][0]) self.assertEquals(
("GET", "/", None, b"\xe4\xbd\xa0\xe5\xa5\xbd\xed\xa9\xaa"),
t.get_connection().calls[0][0],
)
def test_kwargs_passed_on_to_connections(self): def test_kwargs_passed_on_to_connections(self):
t = Transport([{'host': 'google.com'}], port=123) t = Transport([{"host": "google.com"}], port=123)
self.assertEquals(1, len(t.connection_pool.connections)) self.assertEquals(1, len(t.connection_pool.connections))
self.assertEquals('http://google.com:123', t.connection_pool.connections[0].host) self.assertEquals(
"http://google.com:123", t.connection_pool.connections[0].host
)
def test_kwargs_passed_on_to_connection_pool(self): def test_kwargs_passed_on_to_connection_pool(self):
dt = object() dt = object()
@@ -126,6 +146,7 @@ class TestTransport(TestCase):
class MyConnection(object): class MyConnection(object):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.kwargs = kwargs self.kwargs = kwargs
t = Transport([{}], connection_class=MyConnection) t = Transport([{}], connection_class=MyConnection)
self.assertEquals(1, len(t.connection_pool.connections)) self.assertEquals(1, len(t.connection_pool.connections))
self.assertIsInstance(t.connection_pool.connections[0], MyConnection) self.assertIsInstance(t.connection_pool.connections[0], MyConnection)
@@ -135,18 +156,26 @@ class TestTransport(TestCase):
t.add_connection({"host": "google.com", "port": 1234}) t.add_connection({"host": "google.com", "port": 1234})
self.assertEquals(2, len(t.connection_pool.connections)) self.assertEquals(2, len(t.connection_pool.connections))
self.assertEquals('http://google.com:1234', t.connection_pool.connections[1].host) self.assertEquals(
"http://google.com:1234", t.connection_pool.connections[1].host
)
def test_request_will_fail_after_X_retries(self): def test_request_will_fail_after_X_retries(self):
t = Transport([{'exception': ConnectionError('abandon ship')}], connection_class=DummyConnection) t = Transport(
[{"exception": ConnectionError("abandon ship")}],
connection_class=DummyConnection,
)
self.assertRaises(ConnectionError, t.perform_request, 'GET', '/') self.assertRaises(ConnectionError, t.perform_request, "GET", "/")
self.assertEquals(4, len(t.get_connection().calls)) self.assertEquals(4, len(t.get_connection().calls))
def test_failed_connection_will_be_marked_as_dead(self): def test_failed_connection_will_be_marked_as_dead(self):
t = Transport([{'exception': ConnectionError('abandon ship')}] * 2, connection_class=DummyConnection) t = Transport(
[{"exception": ConnectionError("abandon ship")}] * 2,
connection_class=DummyConnection,
)
self.assertRaises(ConnectionError, t.perform_request, 'GET', '/') self.assertRaises(ConnectionError, t.perform_request, "GET", "/")
self.assertEquals(0, len(t.connection_pool.connections)) self.assertEquals(0, len(t.connection_pool.connections))
def test_resurrected_connection_will_be_marked_as_live_on_success(self): def test_resurrected_connection_will_be_marked_as_live_on_success(self):
@@ -156,35 +185,57 @@ class TestTransport(TestCase):
t.connection_pool.mark_dead(con1) t.connection_pool.mark_dead(con1)
t.connection_pool.mark_dead(con2) t.connection_pool.mark_dead(con2)
t.perform_request('GET', '/') t.perform_request("GET", "/")
self.assertEquals(1, len(t.connection_pool.connections)) self.assertEquals(1, len(t.connection_pool.connections))
self.assertEquals(1, len(t.connection_pool.dead_count)) self.assertEquals(1, len(t.connection_pool.dead_count))
def test_sniff_will_use_seed_connections(self): def test_sniff_will_use_seed_connections(self):
t = Transport([{'data': CLUSTER_NODES}], connection_class=DummyConnection) t = Transport([{"data": CLUSTER_NODES}], connection_class=DummyConnection)
t.set_connections([{'data': 'invalid'}]) t.set_connections([{"data": "invalid"}])
t.sniff_hosts() t.sniff_hosts()
self.assertEquals(1, len(t.connection_pool.connections)) self.assertEquals(1, len(t.connection_pool.connections))
self.assertEquals('http://1.1.1.1:123', t.get_connection().host) self.assertEquals("http://1.1.1.1:123", t.get_connection().host)
def test_sniff_on_start_fetches_and_uses_nodes_list(self): def test_sniff_on_start_fetches_and_uses_nodes_list(self):
t = Transport([{'data': CLUSTER_NODES}], connection_class=DummyConnection, sniff_on_start=True) t = Transport(
[{"data": CLUSTER_NODES}],
connection_class=DummyConnection,
sniff_on_start=True,
)
self.assertEquals(1, len(t.connection_pool.connections)) self.assertEquals(1, len(t.connection_pool.connections))
self.assertEquals('http://1.1.1.1:123', t.get_connection().host) self.assertEquals("http://1.1.1.1:123", t.get_connection().host)
def test_sniff_on_start_ignores_sniff_timeout(self): def test_sniff_on_start_ignores_sniff_timeout(self):
t = Transport([{'data': CLUSTER_NODES}], connection_class=DummyConnection, sniff_on_start=True, sniff_timeout=12) t = Transport(
self.assertEquals((('GET', '/_nodes/_all/http'), {'timeout': None}), t.seed_connections[0].calls[0]) [{"data": CLUSTER_NODES}],
connection_class=DummyConnection,
sniff_on_start=True,
sniff_timeout=12,
)
self.assertEquals(
(("GET", "/_nodes/_all/http"), {"timeout": None}),
t.seed_connections[0].calls[0],
)
def test_sniff_uses_sniff_timeout(self): def test_sniff_uses_sniff_timeout(self):
t = Transport([{'data': CLUSTER_NODES}], connection_class=DummyConnection, sniff_timeout=42) t = Transport(
[{"data": CLUSTER_NODES}],
connection_class=DummyConnection,
sniff_timeout=42,
)
t.sniff_hosts() t.sniff_hosts()
self.assertEquals((('GET', '/_nodes/_all/http'), {'timeout': 42}), t.seed_connections[0].calls[0]) self.assertEquals(
(("GET", "/_nodes/_all/http"), {"timeout": 42}),
t.seed_connections[0].calls[0],
)
def test_sniff_reuses_connection_instances_if_possible(self): def test_sniff_reuses_connection_instances_if_possible(self):
t = Transport([{'data': CLUSTER_NODES}, {"host": "1.1.1.1", "port": 123}], connection_class=DummyConnection, randomize_hosts=False) t = Transport(
[{"data": CLUSTER_NODES}, {"host": "1.1.1.1", "port": 123}],
connection_class=DummyConnection,
randomize_hosts=False,
)
connection = t.connection_pool.connections[1] connection = t.connection_pool.connections[1]
t.sniff_hosts() t.sniff_hosts()
@@ -192,25 +243,32 @@ class TestTransport(TestCase):
self.assertIs(connection, t.get_connection()) self.assertIs(connection, t.get_connection())
def test_sniff_on_fail_triggers_sniffing_on_fail(self): def test_sniff_on_fail_triggers_sniffing_on_fail(self):
t = Transport([{'exception': ConnectionError('abandon ship')}, {"data": CLUSTER_NODES}], t = Transport(
connection_class=DummyConnection, sniff_on_connection_fail=True, max_retries=0, randomize_hosts=False) [{"exception": ConnectionError("abandon ship")}, {"data": CLUSTER_NODES}],
connection_class=DummyConnection,
sniff_on_connection_fail=True,
max_retries=0,
randomize_hosts=False,
)
self.assertRaises(ConnectionError, t.perform_request, 'GET', '/') self.assertRaises(ConnectionError, t.perform_request, "GET", "/")
self.assertEquals(1, len(t.connection_pool.connections)) self.assertEquals(1, len(t.connection_pool.connections))
self.assertEquals('http://1.1.1.1:123', t.get_connection().host) self.assertEquals("http://1.1.1.1:123", t.get_connection().host)
def test_sniff_after_n_seconds(self): def test_sniff_after_n_seconds(self):
t = Transport([{"data": CLUSTER_NODES}], t = Transport(
connection_class=DummyConnection, sniffer_timeout=5) [{"data": CLUSTER_NODES}],
connection_class=DummyConnection,
sniffer_timeout=5,
)
for _ in range(4): for _ in range(4):
t.perform_request('GET', '/') t.perform_request("GET", "/")
self.assertEquals(1, len(t.connection_pool.connections)) self.assertEquals(1, len(t.connection_pool.connections))
self.assertIsInstance(t.get_connection(), DummyConnection) self.assertIsInstance(t.get_connection(), DummyConnection)
t.last_sniff = time.time() - 5.1 t.last_sniff = time.time() - 5.1
t.perform_request('GET', '/') t.perform_request("GET", "/")
self.assertEquals(1, len(t.connection_pool.connections)) self.assertEquals(1, len(t.connection_pool.connections))
self.assertEquals('http://1.1.1.1:123', t.get_connection().host) self.assertEquals("http://1.1.1.1:123", t.get_connection().host)
self.assertTrue(time.time() - 1 < t.last_sniff < time.time() + 0.01 ) self.assertTrue(time.time() - 1 < t.last_sniff < time.time() + 0.01)