diff --git a/.ci/test-matrix.yml b/.ci/test-matrix.yml index a78b2fc48..dfebbbaa2 100644 --- a/.ci/test-matrix.yml +++ b/.ci/test-matrix.yml @@ -1,12 +1,11 @@ --- ELASTICSEARCH_VERSION: -- 7.0.0-beta1 -- 6.6.2 +- 7.0.0 +- 7.0.1 PYTHON_VERSION: - 2.7 -- 3.4 - 3.5 - 3.6 - 3.7 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..794e82344 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,27 @@ +language: python + +services: + - docker + +python: + - "2.7" + - "3.4" + - "3.5" + - "3.6" + - "3.7" + - "3.8" + +env: + # different connection classes to test + - TEST_ES_CONNECTION=Urllib3HttpConnection + - TEST_ES_CONNECTION=RequestsHttpConnection + +before_install: + - docker run --rm --detach --publish 9200:9200 --env "discovery.type=single-node" -e path.repo=/tmp -e "repositories.url.allowed_urls=http://*" -e node.attr.testattr=test -e node.name=test --name elasticsearch docker.elastic.co/elasticsearch/elasticsearch:7.4.0 + +install: + - git clone https://github.com/elastic/elasticsearch.git ../elasticsearch + - pip install .[develop] + +script: + - python setup.py test diff --git a/Changelog.rst b/Changelog.rst index f166e2d4e..efe822a13 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -3,14 +3,48 @@ Changelog ========= -7.0.0 (dev) ------------ +7.1.0 (2019-11-14) +------------------ + * Fix sniffing with ``http.publish_host`` + * Fix ``request_timeout`` for ``indices`` APIs + * Allow access to ``x-pack`` features without ``xpack`` namespace + * Fix mark dead + +7.0.5 (2019-10-01) +------------------ + * Fix ``verify_certs=False`` + +7.0.4 (2019-08-22) +------------------ + * Fix wheel distribution + +7.0.3 (2019-08-21) +------------------ + * remove sleep in retries + * pass ``scroll_id`` through body in ``scroll`` + * add ``user-agent`` + +7.0.2 (2019-05-29) +------------------ + * Add connection parameter for Elastic Cloud cloud_id. + * ML client uses client object for _bulk_body requests + +7.0.1 (2019-05-19) +------------------ + * Use black to format the code. + * Update the test matrix to only use current pythons and 7.x ES + * Blocking pool must fit thread_count + * Update client to support missing ES 7 API's and query params. + +7.0.0 (2019-04-11) +------------------ * Removed deprecated option ``update_all_types``. * Using insecure SSL configuration (``verify_cert=False``) raises a warning, this can be not showed with ``ssl_show_warn=False`` * Add support for 7.x api's in Elasticsearch both xpack and oss flavors + 6.3.0 (2018-06-20) ------------ +------------------ * Add an exponential wait on delays * Fix issues with dependencies diff --git a/README b/README index 0b22fb447..ecebbaba7 100644 --- a/README +++ b/README @@ -80,15 +80,22 @@ Simple use-case:: # create an index in elasticsearch, ignore status code 400 (index already exists) >>> es.indices.create(index='my-index', ignore=400) - {u'acknowledged': True} + {'acknowledged': True, 'shards_acknowledged': True, 'index': 'my-index'} # datetimes will be serialized >>> es.index(index="my-index", id=42, body={"any": "data", "timestamp": datetime.now()}) - {u'_id': u'42', u'_index': u'my-index', u'_type': u'test-type', u'_version': 1, u'ok': True} + {'_index': 'my-index', + '_type': '_doc', + '_id': '42', + '_version': 1, + 'result': 'created', + '_shards': {'total': 2, 'successful': 1, 'failed': 0}, + '_seq_no': 0, + '_primary_term': 1} # but not deserialized >>> es.get(index="my-index", id=42)['_source'] - {u'any': u'data', u'timestamp': u'2013-05-12T19:45:31.804229'} + {'any': 'data', 'timestamp': '2019-05-17T17:28:10.329598'} `Full documentation`_. @@ -97,7 +104,7 @@ Simple use-case:: Elastic Cloud (and SSL) use-case:: >>> from elasticsearch import Elasticsearch - >>> es = Elasticsearch("https://elasticsearch.url:port", http_auth=('elastic','yourpassword')) + >>> es = Elasticsearch(cloud_id="", http_auth=('elastic','yourpassword')) >>> es.info() Using SSL Context with a self-signed cert use-case:: diff --git a/docs/conf.py b/docs/conf.py index 38b0b26d4..edd8be947 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,34 +16,34 @@ # 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 # 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 ----------------------------------------------------- # 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 # 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" # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix of source filenames. -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'Elasticsearch' -copyright = u'2013, Honza Král' +project = u"Elasticsearch" +copyright = u"2013, Honza Král" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -51,6 +51,7 @@ # import elasticsearch + # The short X.Y version. version = elasticsearch.__versionstr__ # The full version, including alpha/beta/rc tags. @@ -58,40 +59,40 @@ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. -#language = None +# language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # 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 # 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. -#default_role = None +# default_role = None # 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 # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # 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. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # -- Options for HTML output --------------------------------------------------- @@ -99,11 +100,12 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # 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 import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' + + html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme @@ -113,116 +115,119 @@ # 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 # documentation. -#html_theme_options = {} +# html_theme_options = {} # 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 # " v documentation". -#html_title = None +# html_title = None # 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 # 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 # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # 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, # 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, # 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 # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # 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 # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # 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. -#html_split_index = False +# html_split_index = False # 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. -#html_show_sphinx = True +# html_show_sphinx = 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 # contain a tag referring to it. The value of this option must be the # 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"). -#html_file_suffix = None +# html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = 'Elasticsearchdoc' +htmlhelp_basename = "Elasticsearchdoc" # -- Options for LaTeX output -------------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). 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 title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # 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. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output -------------------------------------------- @@ -230,12 +235,11 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'elasticsearch-py', u'Elasticsearch Documentation', - [u'Honza Král'], 1) + ("index", "elasticsearch-py", u"Elasticsearch Documentation", [u"Honza Král"], 1) ] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------------ @@ -244,19 +248,25 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'Elasticsearch', u'Elasticsearch Documentation', - u'Honza Král', 'Elasticsearch', 'One line description of project.', - 'Miscellaneous'), + ( + "index", + "Elasticsearch", + u"Elasticsearch Documentation", + u"Honza Král", + "Elasticsearch", + "One line description of project.", + "Miscellaneous", + ) ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # 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. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False diff --git a/docs/exceptions.rst b/docs/exceptions.rst index 100672252..03bb60e1c 100644 --- a/docs/exceptions.rst +++ b/docs/exceptions.rst @@ -21,3 +21,5 @@ Exceptions .. autoclass:: NotFoundError(TransportError) .. autoclass:: ConflictError(TransportError) .. autoclass:: RequestError(TransportError) +.. autoclass:: AuthenticationException(TransportError) +.. autoclass:: AuthorizationException(TransportError) diff --git a/docs/helpers.rst b/docs/helpers.rst index c1b52608f..2202e9bca 100644 --- a/docs/helpers.rst +++ b/docs/helpers.rst @@ -93,6 +93,23 @@ document is like ``{"word": ""}``. For a more complete and complex example please take a look at https://github.com/elastic/elasticsearch-py/blob/master/example/load.py#L76-L130 +The :meth:`~elasticsearch.Elasticsearch.parallel_bulk` api is a wrapper around the :meth:`~elasticsearch.Elasticsearch.bulk` api to provide threading. :meth:`~elasticsearch.Elasticsearch.parallel_bulk` returns a generator which must be consumed to produce results. + +To see the results use: + +.. code:: python + + for success, info in parallel_bulk(...): + if not success: + print('A document failed:', info) + +If you don't care about the results, you can use deque from collections: + +.. code:: python + + from collections import deque + deque(parallel_bulk(...), maxlen=0) + .. note:: When reading raw json strings from a file, you can also pass them in diff --git a/docs/index.rst b/docs/index.rst index d8c01592f..f2d17d6d3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,6 +17,9 @@ Compatibility The library is compatible with all Elasticsearch versions since ``0.90.x`` but you **have to use a matching major version**: +For **Elasticsearch 7.0** and later, use the major version 7 (``7.x.y``) of the +library. + For **Elasticsearch 6.0** and later, use the major version 6 (``6.x.y``) of the library. @@ -29,6 +32,9 @@ library, and so on. The recommended way to set your requirements in your `setup.py` or `requirements.txt` is:: + # Elasticsearch 7.x + elasticsearch>=7.0.0,<8.0.0 + # Elasticsearch 6.x elasticsearch>=6.0.0,<7.0.0 @@ -39,7 +45,7 @@ The recommended way to set your requirements in your `setup.py` or elasticsearch>=2.0.0,<3.0.0 If you have a need to have multiple versions installed at the same time older -versions are also released as ``elasticsearch2`` and ``elasticsearch5``. +versions are also released as ``elasticsearch2``, ``elasticsearch5`` and ``elasticsearch6``. Installation ------------ @@ -63,10 +69,10 @@ Example Usage 'text': 'Elasticsearch: cool. bonsai cool.', 'timestamp': datetime.now(), } - res = es.index(index="test-index", doc_type='tweet', id=1, body=doc) + res = es.index(index="test-index", id=1, body=doc) print(res['result']) - res = es.get(index="test-index", doc_type='tweet', id=1) + res = es.get(index="test-index", id=1) print(res['_source']) es.indices.refresh(index="test-index") @@ -224,6 +230,28 @@ description of the options. .. _certifi: http://certifiio.readthedocs.io/en/latest/ +APIKey Authentication +~~~~~~~~~~~~~~~~~~~~~~ + +You can configure the client to use Elasticsearch's `API Key`_ for connecting to your cluster. +Please note this authentication method has been introduced with release of Elasticsearch ``6.7.0``. + + from elasticsearch import Elasticsearch + + # you can use the api key tuple + es = Elasticsearch( + ['node-1', 'node-2', 'node-3'], + api_key=('id', 'api_key'), + ) + + # or you pass the base 64 encoded token + es = Elasticsearch( + ['node-1', 'node-2', 'node-3'], + api_key='base64encoded tuple', + ) + +.. _API Key: https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html + Logging ~~~~~~~ @@ -262,7 +290,7 @@ Compression ~~~~~~~~~~~ When using capacity-constrained networks (low throughput), it may be handy to enable compression. This is especially useful when doing bulk loads or inserting large -documents. This will configure compression on the *request*. +documents. This will configure compression. :: from elasticsearch import Elasticsearch diff --git a/elasticsearch/__init__.py b/elasticsearch/__init__.py index de5da014f..34b590d6f 100644 --- a/elasticsearch/__init__.py +++ b/elasticsearch/__init__.py @@ -1,7 +1,7 @@ # flake8: noqa from __future__ import absolute_import -VERSION = (7, 0, 0) +VERSION = (7, 1, 0) __version__ = VERSION __versionstr__ = ".".join(map(str, VERSION)) diff --git a/elasticsearch/client/__init__.py b/elasticsearch/client/__init__.py index 55f10bc43..ae5ead602 100644 --- a/elasticsearch/client/__init__.py +++ b/elasticsearch/client/__init__.py @@ -12,7 +12,7 @@ from .remote import RemoteClient from .snapshot import SnapshotClient from .tasks import TasksClient -from .xpack import XPackClient +from .xpack import XPackClient, XPACK_NAMESPACES from .utils import query_params, _make_path, SKIP_IN_PATH logger = logging.getLogger("elasticsearch") @@ -214,6 +214,12 @@ def __init__(self, hosts=None, transport_class=Transport, **kwargs): self.remote = RemoteClient(self) self.snapshot = SnapshotClient(self) self.tasks = TasksClient(self) + + # new style access to x-pack features without .xpack step + for namespace, NamespacedClient in XPACK_NAMESPACES.items(): + setattr(self, namespace, NamespacedClient(self)) + + # old style xpack access self.xpack = XPackClient(self) def __repr__(self): @@ -329,6 +335,11 @@ def index(self, index, body, doc_type="_doc", id=None, params=None): :arg index: The name of the index :arg body: The document :arg id: Document ID + :arg if_primary_term: only perform the index operation if the last + operation that has changed the document has the specified primary + term + :arg if_seq_no: only perform the index operation if the last operation + that has changed the document has the specified sequence number :arg doc_type: Document type, defaults to `_doc`. Not used on ES 7 clusters. :arg op_type: Explicit operation type, default 'index', valid choices are: 'index', 'create' @@ -356,17 +367,12 @@ def index(self, index, body, doc_type="_doc", id=None, params=None): if param in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument.") return self.transport.perform_request( - "POST" if id in SKIP_IN_PATH else "PUT", - _make_path(index, doc_type, id), - params=params, - body=body, + "POST", _make_path(index, doc_type, id), params=params, body=body ) @query_params( "_source", - "_source_exclude", "_source_excludes", - "_source_include", "_source_includes", "parent", "preference", @@ -386,9 +392,9 @@ def exists(self, index, id, doc_type="_doc", params=None): :arg id: The document ID :arg _source: True or false to return the _source field or not, or a list of fields to return - :arg _source_exclude: A list of fields to exclude from the returned + :arg _source_excludes: A list of fields to exclude from the returned _source field - :arg _source_include: A list of fields to extract and return from the + :arg _source_includes: A list of fields to extract and return from the _source field :arg parent: The ID of the parent document :arg preference: Specify the node or shard the operation should be @@ -413,9 +419,7 @@ def exists(self, index, id, doc_type="_doc", params=None): @query_params( "_source", - "_source_exclude", "_source_excludes", - "_source_include", "_source_includes", "parent", "preference", @@ -433,9 +437,9 @@ def exists_source(self, index, id, doc_type=None, params=None): :arg id: The document ID :arg _source: True or false to return the _source field or not, or a list of fields to return - :arg _source_exclude: A list of fields to exclude from the returned + :arg _source_excludes: A list of fields to exclude from the returned _source field - :arg _source_include: A list of fields to extract and return from the + :arg _source_includes: A list of fields to extract and return from the _source field :arg parent: The ID of the parent document :arg preference: Specify the node or shard the operation should be @@ -458,9 +462,7 @@ def exists_source(self, index, id, doc_type=None, params=None): @query_params( "_source", - "_source_exclude", "_source_excludes", - "_source_include", "_source_includes", "parent", "preference", @@ -480,9 +482,9 @@ def get(self, index, id, doc_type="_doc", params=None): :arg id: The document ID :arg _source: True or false to return the _source field or not, or a list of fields to return - :arg _source_exclude: A list of fields to exclude from the returned + :arg _source_excludes: A list of fields to exclude from the returned _source field - :arg _source_include: A list of fields to extract and return from the + :arg _source_includes: A list of fields to extract and return from the _source field :arg parent: The ID of the parent document :arg preference: Specify the node or shard the operation should be @@ -507,9 +509,7 @@ def get(self, index, id, doc_type="_doc", params=None): @query_params( "_source", - "_source_exclude", "_source_excludes", - "_source_include", "_source_includes", "parent", "preference", @@ -528,9 +528,9 @@ def get_source(self, index, id, doc_type="_doc", params=None): :arg id: The document ID :arg _source: True or false to return the _source field or not, or a list of fields to return - :arg _source_exclude: A list of fields to exclude from the returned + :arg _source_excludes: A list of fields to exclude from the returned _source field - :arg _source_include: A list of fields to extract and return from the + :arg _source_includes: A list of fields to extract and return from the _source field :arg parent: The ID of the parent document :arg preference: Specify the node or shard the operation should be @@ -553,9 +553,7 @@ def get_source(self, index, id, doc_type="_doc", params=None): @query_params( "_source", - "_source_exclude", "_source_excludes", - "_source_include", "_source_includes", "preference", "realtime", @@ -574,9 +572,9 @@ def mget(self, body, doc_type=None, index=None, params=None): :arg index: The name of the index :arg _source: True or false to return the _source field or not, or a list of fields to return - :arg _source_exclude: A list of fields to exclude from the returned + :arg _source_excludes: A list of fields to exclude from the returned _source field - :arg _source_include: A list of fields to extract and return from the + :arg _source_includes: A list of fields to extract and return from the _source field :arg preference: Specify the node or shard the operation should be performed on (default: random) @@ -596,9 +594,7 @@ def mget(self, body, doc_type=None, index=None, params=None): @query_params( "_source", - "_source_exclude", "_source_excludes", - "_source_include", "_source_includes", "fields", "if_seq_no", @@ -625,9 +621,9 @@ def update(self, index, id, doc_type="_doc", body=None, params=None): :arg body: The request definition using either `script` or partial `doc` :arg _source: True or false to return the _source field or not, or a list of fields to return - :arg _source_exclude: A list of fields to exclude from the returned + :arg _source_excludes: A list of fields to exclude from the returned _source field - :arg _source_include: A list of fields to extract and return from the + :arg _source_includes: A list of fields to extract and return from the _source field :arg fields: A comma-separated list of fields to return in the response :arg if_seq_no: @@ -639,7 +635,8 @@ def update(self, index, id, doc_type="_doc", body=None, params=None): operation visible to search, if `wait_for` then wait for a refresh to make this operation visible to search, if `false` (the default) then do nothing with refreshes., valid choices are: 'true', 'false', - 'wait_forarg retry_on_conflict: Specify how many times should the operation be + 'wait_for' + :arg retry_on_conflict: Specify how many times should the operation be retried when a conflict occurs (default: 0) :arg routing: Specific routing value :arg timeout: Explicit operation timeout @@ -663,15 +660,14 @@ def update(self, index, id, doc_type="_doc", body=None, params=None): @query_params( "_source", - "_source_exclude", "_source_excludes", - "_source_include", "_source_includes", "allow_no_indices", "allow_partial_search_results", "analyze_wildcard", "analyzer", "batched_reduce_size", + "ccs_minimize_roundtrips", "default_operator", "df", "docvalue_fields", @@ -717,9 +713,9 @@ def search(self, index=None, body=None, params=None): :arg body: The search definition using the Query DSL :arg _source: True or false to return the _source field or not, or a list of fields to return - :arg _source_exclude: A list of fields to exclude from the returned + :arg _source_excludes: A list of fields to exclude from the returned _source field - :arg _source_include: A list of fields to extract and return from the + :arg _source_includes: A list of fields to extract and return from the _source field :arg allow_no_indices: Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` @@ -736,6 +732,9 @@ def search(self, index=None, body=None, params=None): as a protection mechanism to reduce the memory overhead per search request if the potential number of shards in the request can be large., default 512 + :arg ccs_minimize_roundtrips: Indicates whether network round-trips + should be minimized as part of cross-cluster search requests + execution, default 'true' :arg default_operator: The default operator for query string query (AND or OR), default 'OR', valid choices are: 'AND', 'OR' :arg df: The field to use as default where no field prefix is given in @@ -814,9 +813,7 @@ def search(self, index=None, body=None, params=None): @query_params( "_source", - "_source_exclude", "_source_excludes", - "_source_include", "_source_includes", "allow_no_indices", "analyze_wildcard", @@ -861,9 +858,9 @@ def update_by_query(self, index, body=None, params=None): :arg body: The search definition using the Query DSL :arg _source: True or false to return the _source field or not, or a list of fields to return - :arg _source_exclude: A list of fields to exclude from the returned + :arg _source_excludes: A list of fields to exclude from the returned _source field - :arg _source_include: A list of fields to extract and return from the + :arg _source_includes: A list of fields to extract and return from the _source field :arg allow_no_indices: Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` @@ -935,10 +932,28 @@ def update_by_query(self, index, body=None, params=None): "POST", _make_path(index, "_update_by_query"), params=params, body=body ) + @query_params("requests_per_second") + def update_by_query_rethrottle(self, task_id, params=None): + """ + ``_ + + :arg task_id: The task id to rethrottle + :arg requests_per_second: The throttle to set on this request in + floating sub-requests per second. -1 means set no throttle. + """ + if task_id in SKIP_IN_PATH: + raise ValueError("Empty value passed for a required argument 'task_id'.") + return self.transport.perform_request( + "POST", + _make_path("_update_by_query", task_id, "_rethrottle"), + params=params, + ) + @query_params( "refresh", "requests_per_second", "slices", + "scroll", "timeout", "wait_for_active_shards", "wait_for_completion", @@ -956,6 +971,8 @@ def reindex(self, body, params=None): :arg slices: The number of slices this task should be divided into. Defaults to 1 meaning the task isn't sliced into subtasks., default 1 + :arg scroll: Control how long to keep the search context alive, default + '5m' :arg timeout: Time each individual bulk request should wait for shards that are unavailable., default '1m' :arg wait_for_active_shards: Sets the number of shard copies that must @@ -988,9 +1005,7 @@ def reindex_rethrottle(self, task_id=None, params=None): @query_params( "_source", - "_source_exclude", "_source_excludes", - "_source_include", "_source_includes", "allow_no_indices", "analyze_wildcard", @@ -1033,9 +1048,9 @@ def delete_by_query(self, index, body, params=None): :arg body: The search definition using the Query DSL :arg _source: True or false to return the _source field or not, or a list of fields to return - :arg _source_exclude: A list of fields to exclude from the returned + :arg _source_excludes: A list of fields to exclude from the returned _source field - :arg _source_include: A list of fields to extract and return from the + :arg _source_includes: A list of fields to extract and return from the _source field :arg allow_no_indices: Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` @@ -1104,6 +1119,23 @@ def delete_by_query(self, index, body, params=None): "POST", _make_path(index, "_delete_by_query"), params=params, body=body ) + @query_params("requests_per_second") + def delete_by_query_rethrottle(self, task_id, params=None): + """ + ``_ + + :arg task_id: The task id to rethrottle + :arg requests_per_second: The throttle to set on this request in + floating sub-requests per second. -1 means set no throttle. + """ + if task_id in SKIP_IN_PATH: + raise ValueError("Empty value passed for a required argument 'task_id'.") + return self.transport.perform_request( + "POST", + _make_path("_delete_by_query", task_id, "_rethrottle"), + params=params, + ) + @query_params( "allow_no_indices", "expand_wildcards", @@ -1142,12 +1174,14 @@ def search_shards(self, index=None, params=None): @query_params( "allow_no_indices", + "ccs_minimize_roundtrips", "expand_wildcards", "explain", "ignore_unavailable", "preference", "profile", "routing", + "rest_total_hits_as_int", "scroll", "search_type", "typed_keys", @@ -1165,6 +1199,9 @@ def search_template(self, index=None, body=None, params=None): :arg allow_no_indices: Whether to ignore if a wildcard indices expression resolves into no concrete indices. (This includes `_all` string or when no indices have been specified) + :arg ccs_minimize_roundtrips: Indicates whether network round-trips + should be minimized as part of cross-cluster search requests + execution, default 'true' :arg expand_wildcards: Whether to expand wildcard expression to concrete indices that are open, closed or both., default 'open', valid choices are: 'open', 'closed', 'none', 'all' @@ -1176,6 +1213,9 @@ def search_template(self, index=None, body=None, params=None): performed on (default: random) :arg profile: Specify whether to profile the query execution :arg routing: A comma-separated list of specific routing values + :arg rest_total_hits_as_int: Indicates whether hits.total should be + rendered as an integer or an object in the rest search response, + default False :arg scroll: Specify how long a consistent view of the index should be maintained for scrolled search :arg search_type: Search operation type, valid choices are: @@ -1190,9 +1230,7 @@ def search_template(self, index=None, body=None, params=None): @query_params( "_source", - "_source_exclude", "_source_excludes", - "_source_include", "_source_includes", "analyze_wildcard", "analyzer", @@ -1217,9 +1255,9 @@ def explain(self, index, id, doc_type="_doc", body=None, params=None): :arg body: The query definition using the Query DSL :arg _source: True or false to return the _source field or not, or a list of fields to return - :arg _source_exclude: A list of fields to exclude from the returned + :arg _source_excludes: A list of fields to exclude from the returned _source field - :arg _source_include: A list of fields to extract and return from the + :arg _source_includes: A list of fields to extract and return from the _source field :arg analyze_wildcard: Specify whether wildcards and prefix queries in the query string query should be analyzed (default: false) @@ -1245,7 +1283,7 @@ def explain(self, index, id, doc_type="_doc", body=None, params=None): ) @query_params("scroll", "rest_total_hits_as_int") - def scroll(self, scroll_id=None, body=None, params=None): + def scroll(self, body=None, scroll_id=None, params=None): """ Scroll a search request created by specifying the scroll parameter. ``_ @@ -1309,6 +1347,11 @@ def delete(self, index, id, doc_type="_doc", params=None): :arg index: The name of the index :arg id: The document ID + :arg if_primary_term: only perform the delete operation if the last + operation that has changed the document has the specified primary + term + :arg if_seq_no: only perform the delete operation if the last operation + that has changed the document has the specified sequence number :arg parent: ID of parent document :arg refresh: If `true` then refresh the effected shards to make this operation visible to search, if `wait_for` then wait for a refresh @@ -1341,6 +1384,7 @@ def delete(self, index, id, doc_type="_doc", params=None): "df", "expand_wildcards", "ignore_unavailable", + "ignore_throttled", "lenient", "min_score", "preference", @@ -1372,6 +1416,8 @@ def count(self, doc_type=None, index=None, body=None, params=None): choices are: 'open', 'closed', 'none', 'all' :arg ignore_unavailable: Whether specified concrete indices should be ignored when unavailable (missing or closed) + :arg ignore_throttled: Whether specified concrete, expanded or aliased + indices should be ignored when throttled :arg lenient: Specify whether format-based query failures (such as providing text to a numeric field) should be ignored :arg min_score: Include only documents with a specific `_score` value in @@ -1390,9 +1436,7 @@ def count(self, doc_type=None, index=None, body=None, params=None): @query_params( "_source", - "_source_exclude", "_source_excludes", - "_source_include", "_source_includes", "fields", "pipeline", @@ -1415,9 +1459,9 @@ def bulk(self, body, doc_type=None, index=None, params=None): :arg _source: True or false to return the _source field or not, or default list of fields to return, can be overridden on each sub- request - :arg _source_exclude: Default list of fields to exclude from the + :arg _source_excludes: Default list of fields to exclude from the returned _source field, can be overridden on each sub-request - :arg _source_include: Default list of fields to extract and return from + :arg _source_includes: Default list of fields to extract and return from the _source field, can be overridden on each sub-request :arg fields: Default comma-separated list of fields to return in the response for updates, can be overridden on each sub-request @@ -1446,6 +1490,7 @@ def bulk(self, body, doc_type=None, index=None, params=None): ) @query_params( + "ccs_minimize_roundtrips", "max_concurrent_searches", "max_concurrent_shard_requests", "pre_filter_shard_size", @@ -1462,6 +1507,9 @@ def msearch(self, body, index=None, params=None): pairs), separated by newlines :arg index: A list of index names, or a string containing a comma-separated list of index names, to use as the default + :arg ccs_minimize_roundtrips: Indicates whether network round-trips + should be minimized as part of cross-cluster search requests + execution, default 'true' :arg max_concurrent_searches: Controls the maximum number of concurrent searches the multi search api will execute :arg pre_filter_shard_size: A threshold that enforces a pre-filter @@ -1614,7 +1662,7 @@ def mtermvectors(self, doc_type=None, index=None, body=None, params=None): body=body, ) - @query_params() + @query_params("master_timeout", "timeout") def put_script(self, id, body, context=None, params=None): """ Create a script in given language with specified ID. @@ -1622,7 +1670,9 @@ def put_script(self, id, body, context=None, params=None): :arg id: Script ID :arg body: The document - """ + :arg master_timeout: Specify timeout for connection to master + :arg timeout: Explicit operation timeout + """ for param in (id, body): if param in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument.") @@ -1630,13 +1680,38 @@ def put_script(self, id, body, context=None, params=None): "PUT", _make_path("_scripts", id, context), params=params, body=body ) - @query_params() + @query_params("allow_no_indices", "expand_wildcards", "ignore_unavailable") + def rank_eval(self, body, index=None, params=None): + """ + ``_ + + :arg body: The ranking evaluation search definition, including search + requests, document ratings and ranking metric definition. + :arg index: A comma-separated list of index names to search; use `_all` + or empty string to perform the operation on all indices + :arg allow_no_indices: Whether to ignore if a wildcard indices + expression resolves into no concrete indices. (This includes `_all` + string or when no indices have been specified) + :arg expand_wildcards: Whether to expand wildcard expression to concrete + indices that are open, closed or both., default 'open', valid + choices are: 'open', 'closed', 'none', 'all' + :arg ignore_unavailable: Whether specified concrete indices should be + ignored when unavailable (missing or closed) + """ + if body in SKIP_IN_PATH: + raise ValueError("Empty value passed for a required argument 'body'.") + return self.transport.perform_request( + "GET", _make_path(index, "_rank_eval"), params=params, body=body + ) + + @query_params("master_timeout") def get_script(self, id, params=None): """ Retrieve a script from the API. ``_ :arg id: Script ID + :arg master_timeout: Specify timeout for connection to master """ if id in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument 'id'.") @@ -1644,14 +1719,15 @@ def get_script(self, id, params=None): "GET", _make_path("_scripts", id), params=params ) - @query_params() + @query_params("master_timeout", "timeout") def delete_script(self, id, params=None): """ Remove a stored script from elasticsearch. ``_ :arg id: Script ID - """ + :arg master_timeout: Specify timeout for connection to master + :arg timeout: Explicit operation timeout """ if id in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument 'id'.") return self.transport.perform_request( @@ -1670,7 +1746,35 @@ def render_search_template(self, id=None, body=None, params=None): "GET", _make_path("_render", "template", id), params=params, body=body ) - @query_params("max_concurrent_searches", "search_type", "typed_keys") + @query_params("context") + def scripts_painless_context(self, params=None): + """ + `<>`_ + + :arg context: Select a specific context to retrieve API information + about + """ + return self.transport.perform_request( + "GET", "/_scripts/painless/_context", params=params + ) + + @query_params() + def scripts_painless_execute(self, body=None, params=None): + """ + ``_ + + :arg body: The script to execute + """ + return self.transport.perform_request( + "GET", "/_scripts/painless/_execute", params=params, body=body + ) + + @query_params( + "ccs_minimize_roundtrips", + "max_concurrent_searches", + "search_type", + "typed_keys", + ) def msearch_template(self, body, index=None, params=None): """ The /_search/template endpoint allows to use the mustache language to @@ -1682,6 +1786,9 @@ def msearch_template(self, body, index=None, params=None): pairs), separated by newlines :arg index: A list of index names, or a string containing a comma-separated list of index names, to use as the default + :arg ccs_minimize_roundtrips: Indicates whether network round-trips + should be minimized as part of cross-cluster search requests + execution, default 'true' :arg max_concurrent_searches: Controls the maximum number of concurrent searches the multi search api will execute :arg search_type: Search operation type, valid choices are: @@ -1701,7 +1808,11 @@ def msearch_template(self, body, index=None, params=None): ) @query_params( - "allow_no_indices", "expand_wildcards", "fields", "ignore_unavailable" + "allow_no_indices", + "expand_wildcards", + "fields", + "ignore_unavailable", + "include_unmapped", ) def field_caps(self, index=None, body=None, params=None): """ @@ -1721,6 +1832,8 @@ def field_caps(self, index=None, body=None, params=None): :arg fields: A comma-separated list of field names :arg ignore_unavailable: Whether specified concrete indices should be ignored when unavailable (missing or closed) + :arg include_unmapped: Indicates whether unmapped fields should be + included in the response., default False """ return self.transport.perform_request( "GET", _make_path(index, "_field_caps"), params=params, body=body diff --git a/elasticsearch/client/cat.py b/elasticsearch/client/cat.py index 717f99026..46ac8e8b3 100644 --- a/elasticsearch/client/cat.py +++ b/elasticsearch/client/cat.py @@ -1,7 +1,8 @@ from .utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH + 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): """ @@ -19,11 +20,11 @@ def aliases(self, name=None, params=None): by :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', _make_path('_cat', - 'aliases', name), params=params) + return self.transport.perform_request( + "GET", _make_path("_cat", "aliases", name), params=params + ) - @query_params('bytes', 'size', 'format', 'h', 'help', 'local', 'master_timeout', - 's', 'v') + @query_params("bytes", "format", "h", "help", "local", "master_timeout", "s", "v") def allocation(self, node_id=None, params=None): """ Allocation provides a snapshot of how shards have located around the @@ -45,10 +46,11 @@ def allocation(self, node_id=None, params=None): by :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', _make_path('_cat', - 'allocation', node_id), params=params) + return self.transport.perform_request( + "GET", _make_path("_cat", "allocation", node_id), params=params + ) - @query_params('size', 'format', 'h', 'help', 'local', 'master_timeout', 's', 'v') + @query_params("format", "h", "help", "local", "master_timeout", "s", "v") def count(self, index=None, params=None): """ Count provides quick access to the document count of the entire cluster, @@ -68,11 +70,11 @@ def count(self, index=None, params=None): by :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', _make_path('_cat', 'count', - index), params=params) + return self.transport.perform_request( + "GET", _make_path("_cat", "count", index), params=params + ) - @query_params('format', 'h', 'help', 'local', 'master_timeout', 's', 'ts', - 'v') + @query_params("format", "h", "help", "local", "master_timeout", "s", "ts", "v") def health(self, params=None): """ health is a terse, one-line representation of the same information from @@ -91,10 +93,9 @@ def health(self, params=None): :arg ts: Set to false to disable timestamping, default True :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', '/_cat/health', - params=params) + return self.transport.perform_request("GET", "/_cat/health", params=params) - @query_params('help', 's') + @query_params("help", "s") def help(self, params=None): """ A simple help for the cat api. @@ -104,10 +105,21 @@ def help(self, params=None): :arg s: Comma-separated list of column names or column aliases to sort by """ - return self.transport.perform_request('GET', '/_cat', params=params) - - @query_params('bytes', 'time', 'size', 'format', 'h', 'health', 'help', 'local', - 'master_timeout', 'pri', 's', 'v') + return self.transport.perform_request("GET", "/_cat", params=params) + + @query_params( + "bytes", + "size", + "format", + "h", + "health", + "help", + "local", + "master_timeout", + "pri", + "s", + "v", + ) def indices(self, index=None, params=None): """ The indices command provides a cross-section of each index. @@ -133,10 +145,11 @@ def indices(self, index=None, params=None): by :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', _make_path('_cat', - 'indices', index), params=params) + return self.transport.perform_request( + "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): """ Displays the master's node ID, bound IP address, and node name. @@ -153,11 +166,9 @@ def master(self, params=None): by :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', '/_cat/master', - params=params) + return self.transport.perform_request("GET", "/_cat/master", params=params) - @query_params('format', 'full_id', 'h', 'help', 'local', 'master_timeout', - 's', 'v') + @query_params("format", "full_id", "h", "help", "local", "master_timeout", "s", "v") def nodes(self, params=None): """ The nodes command shows the cluster topology. @@ -176,10 +187,11 @@ def nodes(self, params=None): by :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', '/_cat/nodes', - params=params) + return self.transport.perform_request("GET", "/_cat/nodes", 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): """ recovery is a view of shard replication. @@ -198,10 +210,13 @@ def recovery(self, index=None, params=None): by :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', _make_path('_cat', - 'recovery', index), params=params) + return self.transport.perform_request( + "GET", _make_path("_cat", "recovery", index), params=params + ) - @query_params('bytes', 'time', 'size', 'format', 'h', 'help', 'local', 'master_timeout', 's', 'v') + @query_params( + "bytes", "size", "format", "h", "help", "local", "master_timeout", "s", "v" + ) def shards(self, index=None, params=None): """ The shards command is the detailed view of what nodes contain which shards. @@ -222,10 +237,11 @@ def shards(self, index=None, params=None): by :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', _make_path('_cat', - 'shards', index), params=params) + return self.transport.perform_request( + "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): """ The segments command is the detailed view of Lucene segments per index. @@ -242,10 +258,11 @@ def segments(self, index=None, params=None): by :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', _make_path('_cat', - 'segments', index), params=params) + return self.transport.perform_request( + "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): """ pending_tasks provides the same information as the @@ -264,11 +281,11 @@ def pending_tasks(self, params=None): by :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', '/_cat/pending_tasks', - params=params) + return self.transport.perform_request( + "GET", "/_cat/pending_tasks", params=params + ) - @query_params('format', 'h', 'help', 'local', 'master_timeout', 's', 'size', - 'v') + @query_params("format", "h", "help", "local", "master_timeout", "s", "size", "v") def thread_pool(self, thread_pool_patterns=None, params=None): """ Get information about thread pools. @@ -289,11 +306,13 @@ def thread_pool(self, thread_pool_patterns=None, params=None): '', 'k', 'm', 'g', 't', 'p' :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', _make_path('_cat', - 'thread_pool', thread_pool_patterns), params=params) + return self.transport.perform_request( + "GET", + _make_path("_cat", "thread_pool", thread_pool_patterns), + params=params, + ) - @query_params('bytes', 'format', 'h', 'help', 'local', 'master_timeout', - 's', 'v') + @query_params("bytes", "format", "h", "help", "local", "master_timeout", "s", "v") def fielddata(self, fields=None, params=None): """ Shows information about currently loaded fielddata on a per-node basis. @@ -314,10 +333,11 @@ def fielddata(self, fields=None, params=None): by :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', _make_path('_cat', - 'fielddata', fields), params=params) + return self.transport.perform_request( + "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): """ @@ -334,10 +354,9 @@ def plugins(self, params=None): by :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', '/_cat/plugins', - params=params) + return self.transport.perform_request("GET", "/_cat/plugins", 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): """ @@ -354,10 +373,9 @@ def nodeattrs(self, params=None): by :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', '/_cat/nodeattrs', - params=params) + return self.transport.perform_request("GET", "/_cat/nodeattrs", 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): """ @@ -374,11 +392,13 @@ def repositories(self, params=None): by :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', '/_cat/repositories', - params=params) + return self.transport.perform_request( + "GET", "/_cat/repositories", params=params + ) - @query_params('format', 'h', 'help', 'ignore_unavailable', 'master_timeout', - 's', 'v') + @query_params( + "format", "h", "help", "ignore_unavailable", "master_timeout", "s", "v" + ) def snapshots(self, repository, params=None): """ @@ -399,11 +419,22 @@ def snapshots(self, repository, params=None): """ if repository in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument 'repository'.") - return self.transport.perform_request('GET', _make_path('_cat', - 'snapshots', repository), params=params) - - @query_params('actions', 'detailed', 'format', 'h', 'help', 'nodes', - 'parent_task_id', 's', 'v') + return self.transport.perform_request( + "GET", _make_path("_cat", "snapshots", repository), params=params + ) + + @query_params( + "actions", + "detailed", + "format", + "h", + "help", + "nodes", + "node_id", + "parent_task_id", + "s", + "v", + ) def tasks(self, params=None): """ @@ -416,6 +447,10 @@ def tasks(self, params=None): :arg h: Comma-separated list of column names to display :arg help: Return help information, default False :arg nodes: A comma-separated list of node IDs or names to limit the + returned information; use `_local` to return information from the + node you're connecting to, leave empty to get information from all + nodes (used for older version of Elasticsearch) + :arg node_id: A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the node you're connecting to, leave empty to get information from all nodes @@ -425,10 +460,9 @@ def tasks(self, params=None): by :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', '/_cat/tasks', - params=params) + return self.transport.perform_request("GET", "/_cat/tasks", 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): """ ``_ @@ -445,6 +479,6 @@ def templates(self, name=None, params=None): by :arg v: Verbose mode. Display column headers, default False """ - return self.transport.perform_request('GET', _make_path('_cat', - 'templates', name), params=params) - + return self.transport.perform_request( + "GET", _make_path("_cat", "templates", name), params=params + ) diff --git a/elasticsearch/client/cluster.py b/elasticsearch/client/cluster.py index d1da9ea9b..361f77542 100644 --- a/elasticsearch/client/cluster.py +++ b/elasticsearch/client/cluster.py @@ -1,16 +1,29 @@ from .utils import NamespacedClient, query_params, _make_path + class ClusterClient(NamespacedClient): - @query_params('level', 'local', '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') + @query_params( + "expand_wildcards", + "level", + "local", + "master_timeout", + "timeout", + "wait_for_active_shards", + "wait_for_events", + "wait_for_no_initializing_shards", + "wait_for_no_relocating_shards", + "wait_for_nodes", + "wait_for_status", + ) def health(self, index=None, params=None): """ Get a very simple status on the health of the cluster. ``_ :arg index: Limit the information returned to a specific index + :arg expand_wildcards: Whether to expand wildcard expression to concrete + indices that are open, closed or both., default 'all', valid choices + are: 'open', 'closed', 'none', 'all' :arg level: Specify the level of detail for returned information, default 'cluster', valid choices are: 'cluster', 'indices', 'shards' :arg local: Return local information, do not retrieve the state from @@ -30,10 +43,11 @@ def health(self, index=None, params=None): :arg wait_for_status: Wait until cluster is in a specific state, default None, valid choices are: 'green', 'yellow', 'red' """ - return self.transport.perform_request('GET', _make_path('_cluster', - 'health', index), params=params) + return self.transport.perform_request( + "GET", _make_path("_cluster", "health", index), params=params + ) - @query_params('local', 'master_timeout') + @query_params("local", "master_timeout") def pending_tasks(self, params=None): """ The pending cluster tasks API returns a list of any cluster-level @@ -45,11 +59,20 @@ def pending_tasks(self, params=None): master node (default: false) :arg master_timeout: Specify timeout for connection to master """ - return self.transport.perform_request('GET', - '/_cluster/pending_tasks', params=params) - - @query_params('allow_no_indices', 'expand_wildcards', 'flat_settings', - 'ignore_unavailable', 'local', 'master_timeout') + return self.transport.perform_request( + "GET", "/_cluster/pending_tasks", params=params + ) + + @query_params( + "allow_no_indices", + "expand_wildcards", + "flat_settings", + "ignore_unavailable", + "local", + "master_timeout", + "wait_for_metadata_version", + "wait_for_timeout", + ) def state(self, metric=None, index=None, params=None): """ Get a comprehensive state information of the whole cluster. @@ -70,13 +93,18 @@ def state(self, metric=None, index=None, params=None): :arg local: Return local information, do not retrieve the state from master node (default: false) :arg master_timeout: Specify timeout for connection to master + :arg wait_for_metadata_version: Wait for the metadata version to be + equal or greater than the specified metadata version + :arg wait_for_timeout: The maximum time to wait for + wait_for_metadata_version before timing out """ if index and not metric: - metric = '_all' - return self.transport.perform_request('GET', _make_path('_cluster', - 'state', metric, index), params=params) + metric = "_all" + return self.transport.perform_request( + "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): """ The Cluster Stats API allows to retrieve statistics from a cluster wide @@ -86,18 +114,20 @@ def stats(self, node_id=None, params=None): :arg node_id: A comma-separated list of node IDs or names to limit the returned information; use `_local` to return information from the - node you're connecting to, leave empty to get information from all + node you're connecting to, `_master` to return information from the + currently-elected master node or leave empty to get information from all nodes :arg flat_settings: Return settings in flat format (default: false) :arg timeout: Explicit operation timeout """ - url = '/_cluster/stats' + url = "/_cluster/stats" if node_id: - url = _make_path('_cluster/stats/nodes', node_id) - return self.transport.perform_request('GET', url, params=params) + url = _make_path("_cluster","stats","nodes", node_id) + return self.transport.perform_request("GET", url, params=params) - @query_params('dry_run', 'explain', 'master_timeout', 'metric', - 'retry_failed', 'timeout') + @query_params( + "dry_run", "explain", "master_timeout", "metric", "retry_failed", "timeout" + ) def reroute(self, body=None, params=None): """ Explicitly execute a cluster reroute allocation command including specific commands. @@ -117,11 +147,11 @@ def reroute(self, body=None, params=None): too many subsequent allocation failures :arg timeout: Explicit operation timeout """ - return self.transport.perform_request('POST', '/_cluster/reroute', - params=params, body=body) + return self.transport.perform_request( + "POST", "/_cluster/reroute", params=params, body=body + ) - @query_params('flat_settings', 'include_defaults', 'master_timeout', - 'timeout') + @query_params("flat_settings", "include_defaults", "master_timeout", "timeout") def get_settings(self, params=None): """ Get cluster settings. @@ -134,10 +164,11 @@ def get_settings(self, params=None): node :arg timeout: Explicit operation timeout """ - return self.transport.perform_request('GET', '/_cluster/settings', - params=params) + return self.transport.perform_request( + "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): """ Update cluster wide specific settings. @@ -150,10 +181,18 @@ def put_settings(self, body=None, params=None): node :arg timeout: Explicit operation timeout """ - return self.transport.perform_request('PUT', '/_cluster/settings', - params=params, body=body) + return self.transport.perform_request( + "PUT", "/_cluster/settings", params=params, body=body + ) - @query_params('include_disk_info', 'include_yes_decisions') + @query_params() + def remote_info(self, params=None): + """ + ``_ + """ + return self.transport.perform_request("GET", "/_remote/info", params=params) + + @query_params("include_disk_info", "include_yes_decisions") def allocation_explain(self, body=None, params=None): """ ``_ @@ -165,6 +204,6 @@ def allocation_explain(self, body=None, params=None): :arg include_yes_decisions: Return 'YES' decisions in explanation (default: false) """ - return self.transport.perform_request('GET', - '/_cluster/allocation/explain', params=params, body=body) - + return self.transport.perform_request( + "GET", "/_cluster/allocation/explain", params=params, body=body + ) diff --git a/elasticsearch/client/indices.py b/elasticsearch/client/indices.py index 6667c8bd8..979f3ebf8 100644 --- a/elasticsearch/client/indices.py +++ b/elasticsearch/client/indices.py @@ -79,7 +79,10 @@ def flush(self, index=None, params=None): ) @query_params( - "master_timeout", "timeout", "wait_for_active_shards", "include_type_name" + "master_timeout", + "timeout", + "wait_for_active_shards", + "include_type_name", ) def create(self, index, body=None, params=None): """ @@ -101,6 +104,32 @@ def create(self, index, body=None, params=None): "PUT", _make_path(index), params=params, body=body ) + @query_params( + "master_timeout", + "timeout", + "wait_for_active_shards" + ) + def clone(self, index, target, body=None, params=None): + """ + Clones an index + ``_ + + :arg index: The name of the source index to clone + :arg target: The name of the target index to clone into + :arg master_timeout: Specify timeout for connection to master + :arg timeout: Explicit operation timeout + :arg wait_for_active_shards: Set the number of active shards to wait for + before the operation returns. + """ + if index in SKIP_IN_PATH: + raise ValueError("Empty value passed for a required argument 'index'.") + if target in SKIP_IN_PATH: + raise ValueError("Empty value passed for a required argument 'target'.") + + return self.transport.perform_request( + "PUT", _make_path(index, '_clone', target), params=params, body=body + ) + @query_params( "allow_no_indices", "expand_wildcards", @@ -109,6 +138,7 @@ def create(self, index, body=None, params=None): "include_defaults", "local", "include_type_name", + "master_timeout", ) def get(self, index, feature=None, params=None): """ @@ -129,6 +159,7 @@ def get(self, index, feature=None, params=None): master node (default: false) :arg include_type_name: Specify whether requests and responses should include a type name (default: depends on Elasticsearch version). + :arg master_timeout: Specify timeout for connection to master """ if index in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument 'index'.") @@ -142,6 +173,7 @@ def get(self, index, feature=None, params=None): "ignore_unavailable", "master_timeout", "timeout", + "wait_for_active_shards", ) def open(self, index, params=None): """ @@ -159,6 +191,8 @@ def open(self, index, params=None): ignored when unavailable (missing or closed) :arg master_timeout: Specify timeout for connection to master :arg timeout: Explicit operation timeout + :arg wait_for_active_shards: Sets the number of active shards to wait + for before the operation returns. """ if index in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument 'index'.") @@ -171,7 +205,7 @@ def open(self, index, params=None): "expand_wildcards", "ignore_unavailable", "master_timeout", - "timeout", + "wait_for_active_shards", ) def close(self, index, params=None): """ @@ -189,7 +223,8 @@ def close(self, index, params=None): :arg ignore_unavailable: Whether specified concrete indices should be ignored when unavailable (missing or closed) :arg master_timeout: Specify timeout for connection to master - :arg timeout: Explicit operation timeout + :arg wait_for_active_shards: Sets the number of active shards to wait + for before the operation returns. """ if index in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument 'index'.") @@ -201,8 +236,8 @@ def close(self, index, params=None): "allow_no_indices", "expand_wildcards", "ignore_unavailable", - "master_timeout", "timeout", + "master_timeout", ) def delete(self, index, params=None): """ @@ -296,8 +331,8 @@ def put_mapping(self, body, doc_type=None, index=None, params=None): Register specific mapping definition for a specific type. ``_ - :arg doc_type: The name of the document type :arg body: The mapping definition + :arg doc_type: The name of the document type :arg index: A comma-separated list of index names the mapping should be added to (supports wildcards); use `_all` or omit to add the mapping on all indices. @@ -327,6 +362,7 @@ def put_mapping(self, body, doc_type=None, index=None, params=None): "ignore_unavailable", "local", "include_type_name", + "master_timeout", ) def get_mapping(self, index=None, doc_type=None, params=None): """ @@ -347,6 +383,7 @@ def get_mapping(self, index=None, doc_type=None, params=None): master node (default: false) :arg include_type_name: Specify whether requests and responses should include a type name (default: depends on Elasticsearch version). + :arg master_timeout: Specify timeout for connection to master """ return self.transport.perform_request( "GET", _make_path(index, "_mapping", doc_type), params=params @@ -391,7 +428,7 @@ def get_field_mapping(self, fields, index=None, doc_type=None, params=None): params=params, ) - @query_params("master_timeout", "timeout") + @query_params("master_timeout") def put_alias(self, index, name, body=None, params=None): """ Create an alias for a specific index/indices. @@ -403,7 +440,6 @@ def put_alias(self, index, name, body=None, params=None): :arg name: The name of the alias to be created or updated :arg body: The settings for the alias, such as `routing` or `filter` :arg master_timeout: Specify timeout for connection to master - :arg timeout: Explicit timeout for the operation """ for param in (index, name): if param in SKIP_IN_PATH: @@ -589,6 +625,7 @@ def delete_template(self, name, params=None): "ignore_unavailable", "include_defaults", "local", + "master_timeout", ) def get_settings(self, index=None, name=None, params=None): """ @@ -611,6 +648,7 @@ def get_settings(self, index=None, name=None, params=None): the indices., default False :arg local: Return local information, do not retrieve the state from master node (default: false) + :arg master_timeout: Specify timeout for connection to master """ return self.transport.perform_request( "GET", _make_path(index, "_settings", name), params=params @@ -622,6 +660,7 @@ def get_settings(self, index=None, name=None, params=None): "flat_settings", "ignore_unavailable", "master_timeout", + "timeout", "preserve_existing", ) def put_settings(self, body, index=None, params=None): @@ -645,6 +684,7 @@ def put_settings(self, body, index=None, params=None): :arg preserve_existing: Whether to update existing settings. If set to `true` existing settings on an index remain unchanged, the default is `false` + :arg timeout: Explicit operation timeout """ if body in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument 'body'.") @@ -654,10 +694,13 @@ def put_settings(self, body, index=None, params=None): @query_params( "completion_fields", + "expand_wildcards", "fielddata_fields", "fields", + "forbid_closed_indices", "groups", "include_segment_file_sizes", + "include_unloaded_segments", "level", "types", ) @@ -671,15 +714,24 @@ def stats(self, index=None, metric=None, params=None): :arg metric: Limit the information returned the specific metrics. :arg completion_fields: A comma-separated list of fields for `fielddata` and `suggest` index metric (supports wildcards) + :arg expand_wildcards: Whether to expand wildcard expression to concrete + indices that are open, closed or both., default 'open', valid + choices are: 'open', 'closed', 'none', 'all' :arg fielddata_fields: A comma-separated list of fields for `fielddata` index metric (supports wildcards) :arg fields: A comma-separated list of fields for `fielddata` and `completion` index metric (supports wildcards) + :arg forbid_closed_indices: If set to false stats will also collected + from closed indices if explicitly specified or if expand_wildcards + expands to closed indices, default True :arg groups: A comma-separated list of search groups for `search` index metric :arg include_segment_file_sizes: Whether to report the aggregated disk usage of each one of the Lucene index files (only applies if segment stats are requested), default False + :arg include_unloaded_segments: If set to true segment stats will + include stats for segments that are not currently loaded into + memory, default False :arg level: Return stats aggregated at cluster, index or shard level, default 'indices', valid choices are: 'cluster', 'indices', 'shards' :arg types: A comma-separated list of document types for the `indexing` @@ -977,14 +1029,15 @@ def forcemerge(self, index=None, params=None): :arg max_num_segments: The number of segments the index should be merged into (default: dynamic) :arg only_expunge_deletes: Specify whether the operation should only - expunge deleted documents - :arg operation_threading: TODO: ? + expunge deleted documents (for pre 7.x ES clusters) """ return self.transport.perform_request( "POST", _make_path(index, "_forcemerge"), params=params ) - @query_params("master_timeout", "timeout", "wait_for_active_shards") + @query_params( + "master_timeout", "timeout", "wait_for_active_shards" + ) def shrink(self, index, target, body=None, params=None): """ The shrink index API allows you to shrink an existing index into a new @@ -1014,6 +1067,27 @@ def shrink(self, index, target, body=None, params=None): "PUT", _make_path(index, "_shrink", target), params=params, body=body ) + @query_params("master_timeout", "timeout", "wait_for_active_shards") + def split(self, index, target, body=None, params=None): + """ + ``_ + + :arg index: The name of the source index to split + :arg target: The name of the target index to split into + :arg body: The configuration for the target index (`settings` and + `aliases`) + :arg master_timeout: Specify timeout for connection to master + :arg timeout: Explicit operation timeout + :arg wait_for_active_shards: Set the number of active shards to wait for + on the shrunken index before the operation returns. + """ + for param in (index, target): + if param in SKIP_IN_PATH: + raise ValueError("Empty value passed for a required argument.") + return self.transport.perform_request( + "PUT", _make_path(index, "_split", target), params=params, body=body + ) + @query_params( "dry_run", "master_timeout", @@ -1050,3 +1124,68 @@ def rollover(self, alias, new_index=None, body=None, params=None): return self.transport.perform_request( "POST", _make_path(alias, "_rollover", new_index), params=params, body=body ) + + # X-pack APIS + @query_params( + "allow_no_indices", + "expand_wildcards", + "ignore_unavailable", + "master_timeout", + "timeout", + "wait_for_active_shards", + ) + def freeze(self, index, params=None): + """ + ``_ + + :arg index: The name of the index to freeze + :arg allow_no_indices: Whether to ignore if a wildcard indices + expression resolves into no concrete indices. (This includes `_all` + string or when no indices have been specified) + :arg expand_wildcards: Whether to expand wildcard expression to concrete + indices that are open, closed or both., default 'closed', valid + choices are: 'open', 'closed', 'none', 'all' + :arg ignore_unavailable: Whether specified concrete indices should be + ignored when unavailable (missing or closed) + :arg master_timeout: Specify timeout for connection to master + :arg timeout: Explicit operation timeout + :arg wait_for_active_shards: Sets the number of active shards to wait + for before the operation returns. + """ + if index in SKIP_IN_PATH: + raise ValueError("Empty value passed for a required argument 'index'.") + return self.transport.perform_request( + "POST", _make_path(index, "_freeze"), params=params + ) + + @query_params( + "allow_no_indices", + "expand_wildcards", + "ignore_unavailable", + "master_timeout", + "timeout", + "wait_for_active_shards", + ) + def unfreeze(self, index, params=None): + """ + ``_ + + :arg index: The name of the index to unfreeze + :arg allow_no_indices: Whether to ignore if a wildcard indices + expression resolves into no concrete indices. (This includes `_all` + string or when no indices have been specified) + :arg expand_wildcards: Whether to expand wildcard expression to concrete + indices that are open, closed or both., default 'closed', valid + choices are: 'open', 'closed', 'none', 'all' + :arg ignore_unavailable: Whether specified concrete indices should be + ignored when unavailable (missing or closed) + :arg master_timeout: Specify timeout for connection to master + :arg timeout: Explicit operation timeout + :arg wait_for_active_shards: Sets the number of active shards to wait + for before the operation returns. + """ + if index in SKIP_IN_PATH: + raise ValueError("Empty value passed for a required argument 'index'.") + return self.transport.perform_request( + "POST", _make_path(index, "_unfreeze"), params=params + ) diff --git a/elasticsearch/client/ingest.py b/elasticsearch/client/ingest.py index 4a2def47e..bc66c331e 100644 --- a/elasticsearch/client/ingest.py +++ b/elasticsearch/client/ingest.py @@ -1,7 +1,8 @@ from .utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH + class IngestClient(NamespacedClient): - @query_params('master_timeout') + @query_params("master_timeout") def get_pipeline(self, id=None, params=None): """ ``_ @@ -10,10 +11,11 @@ def get_pipeline(self, id=None, params=None): :arg master_timeout: Explicit operation timeout for connection to master node """ - return self.transport.perform_request('GET', _make_path('_ingest', - 'pipeline', id), params=params) + return self.transport.perform_request( + "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): """ ``_ @@ -27,10 +29,11 @@ def put_pipeline(self, id, body, params=None): for param in (id, body): if param in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument.") - return self.transport.perform_request('PUT', _make_path('_ingest', - 'pipeline', id), params=params, body=body) + return self.transport.perform_request( + "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): """ ``_ @@ -42,10 +45,11 @@ def delete_pipeline(self, id, params=None): """ if id in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument 'id'.") - return self.transport.perform_request('DELETE', _make_path('_ingest', - 'pipeline', id), params=params) + return self.transport.perform_request( + "DELETE", _make_path("_ingest", "pipeline", id), params=params + ) - @query_params('verbose') + @query_params("verbose") def simulate(self, body, id=None, params=None): """ ``_ @@ -57,5 +61,18 @@ def simulate(self, body, id=None, params=None): """ if body in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument 'body'.") - return self.transport.perform_request('GET', _make_path('_ingest', - 'pipeline', id, '_simulate'), params=params, body=body) + return self.transport.perform_request( + "GET", + _make_path("_ingest", "pipeline", id, "_simulate"), + params=params, + body=body, + ) + + @query_params() + def processor_grok(self, params=None): + """ + ``_ + """ + return self.transport.perform_request( + "GET", "/_ingest/processor/grok", params=params + ) diff --git a/elasticsearch/client/nodes.py b/elasticsearch/client/nodes.py index e009fa898..9549f0620 100644 --- a/elasticsearch/client/nodes.py +++ b/elasticsearch/client/nodes.py @@ -2,12 +2,16 @@ class NodesClient(NamespacedClient): - @query_params() - def reload_secure_settings(self, params=None): + @query_params("timeout") + def reload_secure_settings(self, node_id=None, params=None): """ Reload any settings that have been marked as "reloadable" ``_ + :arg node_id: A comma-separated list of node IDs to span the + reload/reinit call. Should stay empty because reloading usually + involves all cluster nodes. + :arg timeout: Explicit operation timeout """ return self.transport.perform_request( "POST", _make_path("_nodes", "reload_secure_settings"), params=params @@ -82,7 +86,7 @@ def stats(self, node_id=None, metric=None, index_metric=None, params=None): ) @query_params( - "type", "ignore_idle_threads", "interval", "snapshots", "threads", "timeout" + "doc_type", "ignore_idle_threads", "interval", "snapshots", "threads", "timeout" ) def hot_threads(self, node_id=None, params=None): """ diff --git a/elasticsearch/client/remote.py b/elasticsearch/client/remote.py index 9cbb26070..7f859823e 100644 --- a/elasticsearch/client/remote.py +++ b/elasticsearch/client/remote.py @@ -1,11 +1,10 @@ from .utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH + class RemoteClient(NamespacedClient): @query_params() def info(self, params=None): """ ``_ """ - return self.transport.perform_request('GET', '/_remote/info', - params=params) - + return self.transport.perform_request("GET", "/_remote/info", params=params) diff --git a/elasticsearch/client/snapshot.py b/elasticsearch/client/snapshot.py index a57cebedf..88aed52c3 100644 --- a/elasticsearch/client/snapshot.py +++ b/elasticsearch/client/snapshot.py @@ -1,7 +1,8 @@ from .utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH + 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): """ Create a snapshot in repository @@ -18,10 +19,14 @@ def create(self, repository, snapshot, body=None, params=None): for param in (repository, snapshot): if param in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument.") - return self.transport.perform_request('PUT', _make_path('_snapshot', - repository, snapshot), params=params, body=body) - - @query_params('master_timeout') + return self.transport.perform_request( + "PUT", + _make_path("_snapshot", repository, snapshot), + params=params, + body=body, + ) + + @query_params("master_timeout") def delete(self, repository, snapshot, params=None): """ Deletes a snapshot from a repository. @@ -35,10 +40,11 @@ def delete(self, repository, snapshot, params=None): for param in (repository, snapshot): if param in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument.") - return self.transport.perform_request('DELETE', - _make_path('_snapshot', repository, snapshot), params=params) + return self.transport.perform_request( + "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): """ Retrieve information about a snapshot. @@ -56,10 +62,11 @@ def get(self, repository, snapshot, params=None): for param in (repository, snapshot): if param in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument.") - return self.transport.perform_request('GET', _make_path('_snapshot', - repository, snapshot), params=params) + return self.transport.perform_request( + "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): """ Removes a shared file system repository. @@ -72,10 +79,11 @@ def delete_repository(self, repository, params=None): """ if repository in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument 'repository'.") - return self.transport.perform_request('DELETE', - _make_path('_snapshot', repository), params=params) + return self.transport.perform_request( + "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): """ Return information about registered repositories. @@ -87,10 +95,11 @@ def get_repository(self, repository=None, params=None): :arg master_timeout: Explicit operation timeout for connection to master node """ - return self.transport.perform_request('GET', _make_path('_snapshot', - repository), params=params) + return self.transport.perform_request( + "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): """ Registers a shared file system repository. @@ -106,10 +115,11 @@ def create_repository(self, repository, body, params=None): for param in (repository, body): if param in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument.") - return self.transport.perform_request('PUT', _make_path('_snapshot', - repository), params=params, body=body) + return self.transport.perform_request( + "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): """ Restore a snapshot. @@ -126,10 +136,14 @@ def restore(self, repository, snapshot, body=None, params=None): for param in (repository, snapshot): if param in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument.") - return self.transport.perform_request('POST', _make_path('_snapshot', - repository, snapshot, '_restore'), params=params, body=body) - - @query_params('ignore_unavailable', 'master_timeout') + return self.transport.perform_request( + "POST", + _make_path("_snapshot", repository, snapshot, "_restore"), + params=params, + body=body, + ) + + @query_params("ignore_unavailable", "master_timeout") def status(self, repository=None, snapshot=None, params=None): """ Return information about all currently running snapshots. By specifying @@ -144,10 +158,13 @@ def status(self, repository=None, snapshot=None, params=None): :arg master_timeout: Explicit operation timeout for connection to master node """ - return self.transport.perform_request('GET', _make_path('_snapshot', - repository, snapshot, '_status'), params=params) + return self.transport.perform_request( + "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): """ Returns a list of nodes where repository was successfully verified or @@ -161,5 +178,6 @@ def verify_repository(self, repository, params=None): """ if repository in SKIP_IN_PATH: raise ValueError("Empty value passed for a required argument 'repository'.") - return self.transport.perform_request('POST', _make_path('_snapshot', - repository, '_verify'), params=params) + return self.transport.perform_request( + "POST", _make_path("_snapshot", repository, "_verify"), params=params + ) diff --git a/elasticsearch/client/tasks.py b/elasticsearch/client/tasks.py index 8056bfdd9..d98d3bbdc 100644 --- a/elasticsearch/client/tasks.py +++ b/elasticsearch/client/tasks.py @@ -1,8 +1,16 @@ from .utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH + class TasksClient(NamespacedClient): - @query_params('actions', 'detailed', 'group_by', 'nodes', - 'parent_task_id', 'wait_for_completion', 'timeout') + @query_params( + "actions", + "detailed", + "group_by", + "nodes", + "parent_task_id", + "wait_for_completion", + "timeout", + ) def list(self, params=None): """ ``_ @@ -22,9 +30,9 @@ def list(self, params=None): (default: false) :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): """ @@ -41,10 +49,11 @@ def cancel(self, task_id=None, params=None): :arg parent_task_id: Cancel tasks with specified parent task id (node_id:task_number). Set to -1 to cancel all. """ - return self.transport.perform_request('POST', _make_path('_tasks', - task_id, '_cancel'), params=params) + return self.transport.perform_request( + "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): """ Retrieve information for a particular task. @@ -55,5 +64,6 @@ def get(self, task_id=None, params=None): (default: false) :arg timeout: Maximum waiting time for `wait_for_completion` """ - return self.transport.perform_request('GET', _make_path('_tasks', - task_id), params=params) + return self.transport.perform_request( + "GET", _make_path("_tasks", task_id), params=params + ) diff --git a/elasticsearch/client/utils.py b/elasticsearch/client/utils.py index 0c86cd228..9d0099323 100644 --- a/elasticsearch/client/utils.py +++ b/elasticsearch/client/utils.py @@ -3,7 +3,7 @@ import weakref from datetime import date, datetime from functools import wraps -from ..compat import string_types, quote_plus, PY2 +from ..compat import string_types, quote, PY2 # parts of URL to be omitted SKIP_IN_PATH = (None, "", b"", [], ()) @@ -49,7 +49,7 @@ def _make_path(*parts): # TODO: maybe only allow some parts to be lists/tuples ? return "/" + "/".join( # preserve ',' and '*' in url for nicer URLs in logs - quote_plus(_escape(p), b",*") + quote(_escape(p), b",*") for p in parts if p not in SKIP_IN_PATH ) diff --git a/elasticsearch/client/xpack/__init__.py b/elasticsearch/client/xpack/__init__.py index 8c108c082..12d6f0933 100644 --- a/elasticsearch/client/xpack/__init__.py +++ b/elasticsearch/client/xpack/__init__.py @@ -1,28 +1,45 @@ from ..utils import NamespacedClient, query_params +from .ccr import CcrClient +from .data_frame import Data_FrameClient +from .deprecation import DeprecationClient from .graph import GraphClient +from .ilm import IlmClient +from ..indices import IndicesClient from .license import LicenseClient +from .migration import MigrationClient +from .ml import MlClient from .monitoring import MonitoringClient +from .rollup import RollupClient from .security import SecurityClient +from .sql import SqlClient +from .ssl import SslClient from .watcher import WatcherClient -from .ml import MlClient -from .migration import MigrationClient -from .deprecation import DeprecationClient +XPACK_NAMESPACES = { + "ccr": CcrClient, + "data_frame": Data_FrameClient, + "deprecation": DeprecationClient, + "graph": GraphClient, + "ilm": IlmClient, + "indices": IndicesClient, + "license": LicenseClient, + "migration": MigrationClient, + "ml": MlClient, + "monitoring": MonitoringClient, + "rollup": RollupClient, + "security": SecurityClient, + "sql": SqlClient, + "ssl": SslClient, + "watcher": WatcherClient, +} class XPackClient(NamespacedClient): - namespace = "xpack" - def __init__(self, *args, **kwargs): super(XPackClient, self).__init__(*args, **kwargs) - self.graph = GraphClient(self.client) - self.license = LicenseClient(self.client) - self.monitoring = MonitoringClient(self.client) - self.security = SecurityClient(self.client) - self.watcher = WatcherClient(self.client) - self.ml = MlClient(self.client) - self.migration = MigrationClient(self.client) - self.deprecation = DeprecationClient(self.client) + + for namespace in XPACK_NAMESPACES: + setattr(self, namespace, getattr(self.client, namespace)) @query_params("categories", "human") def info(self, params=None): diff --git a/elasticsearch/client/xpack/indices.py b/elasticsearch/client/xpack/indices.py deleted file mode 100644 index c4e488075..000000000 --- a/elasticsearch/client/xpack/indices.py +++ /dev/null @@ -1,67 +0,0 @@ -from ..utils import NamespacedClient, query_params, _make_path, SKIP_IN_PATH - - -class IndicesClient(NamespacedClient): - @query_params( - "allow_no_indices", - "expand_wildcards", - "ignore_unavailable", - "master_timeout", - "timeout", - "wait_for_active_shards", - ) - def freeze(self, index, params=None): - """ - ``_ - - :arg index: The name of the index to freeze - :arg allow_no_indices: Whether to ignore if a wildcard indices - expression resolves into no concrete indices. (This includes `_all` - string or when no indices have been specified) - :arg expand_wildcards: Whether to expand wildcard expression to concrete - indices that are open, closed or both., default 'closed', valid - choices are: 'open', 'closed', 'none', 'all' - :arg ignore_unavailable: Whether specified concrete indices should be - ignored when unavailable (missing or closed) - :arg master_timeout: Specify timeout for connection to master - :arg timeout: Explicit operation timeout - :arg wait_for_active_shards: Sets the number of active shards to wait - for before the operation returns. - """ - if index in SKIP_IN_PATH: - raise ValueError("Empty value passed for a required argument 'index'.") - return self.transport.perform_request( - "POST", _make_path(index, "_freeze"), params=params - ) - - @query_params( - "allow_no_indices", - "expand_wildcards", - "ignore_unavailable", - "master_timeout", - "timeout", - "wait_for_active_shards", - ) - def unfreeze(self, index, params=None): - """ - ``_ - - :arg index: The name of the index to unfreeze - :arg allow_no_indices: Whether to ignore if a wildcard indices - expression resolves into no concrete indices. (This includes `_all` - string or when no indices have been specified) - :arg expand_wildcards: Whether to expand wildcard expression to concrete - indices that are open, closed or both., default 'closed', valid - choices are: 'open', 'closed', 'none', 'all' - :arg ignore_unavailable: Whether specified concrete indices should be - ignored when unavailable (missing or closed) - :arg master_timeout: Specify timeout for connection to master - :arg timeout: Explicit operation timeout - :arg wait_for_active_shards: Sets the number of active shards to wait - for before the operation returns. - """ - if index in SKIP_IN_PATH: - raise ValueError("Empty value passed for a required argument 'index'.") - return self.transport.perform_request( - "POST", _make_path(index, "_unfreeze"), params=params - ) diff --git a/elasticsearch/client/xpack/ml.py b/elasticsearch/client/xpack/ml.py index 9fcb73479..dcccd53a5 100644 --- a/elasticsearch/client/xpack/ml.py +++ b/elasticsearch/client/xpack/ml.py @@ -224,7 +224,7 @@ def find_file_structure(self, body, params=None): "POST", "/_ml/find_file_structure", params=params, - body=self._bulk_body(body), + body=self.client._bulk_body(body), ) @query_params("advance_time", "calc_interim", "end", "skip_time", "start") @@ -634,7 +634,7 @@ def post_data(self, job_id, body, params=None): "POST", _make_path("_ml", "anomaly_detectors", job_id, "_data"), params=params, - body=self._bulk_body(body), + body=self.client._bulk_body(body), ) @query_params() diff --git a/elasticsearch/compat.py b/elasticsearch/compat.py index c3d5fa287..70bcac16c 100644 --- a/elasticsearch/compat.py +++ b/elasticsearch/compat.py @@ -3,13 +3,14 @@ PY2 = sys.version_info[0] == 2 if PY2: - string_types = basestring, - from urllib import quote_plus, urlencode, unquote - from urlparse import urlparse + string_types = (basestring,) + from urllib import quote_plus, quote, urlencode, unquote + from urlparse import urlparse from itertools import imap as map from Queue import Queue else: string_types = str, bytes - from urllib.parse import quote_plus, urlencode, urlparse, unquote + from urllib.parse import quote, quote_plus, urlencode, urlparse, unquote + map = map from queue import Queue diff --git a/elasticsearch/connection/__init__.py b/elasticsearch/connection/__init__.py index 6eb68967d..2470bd39b 100644 --- a/elasticsearch/connection/__init__.py +++ b/elasticsearch/connection/__init__.py @@ -1,4 +1,3 @@ from .base import Connection from .http_requests import RequestsHttpConnection from .http_urllib3 import Urllib3HttpConnection, create_ssl_context - diff --git a/elasticsearch/connection/base.py b/elasticsearch/connection/base.py index d4fbe33de..a3fbd780f 100644 --- a/elasticsearch/connection/base.py +++ b/elasticsearch/connection/base.py @@ -1,17 +1,22 @@ import logging +import base64 + +from platform import python_version + try: import simplejson as json except ImportError: import json from ..exceptions import TransportError, HTTP_EXCEPTIONS +from .. import __versionstr__ -logger = logging.getLogger('elasticsearch') +logger = logging.getLogger("elasticsearch") # create the elasticsearch.trace logger, but only set propagate to False if the # logger hasn't already been configured -_tracer_already_configured = 'elasticsearch.trace' in logging.Logger.manager.loggerDict -tracer = logging.getLogger('elasticsearch.trace') +_tracer_already_configured = "elasticsearch.trace" in logging.Logger.manager.loggerDict +tracer = logging.getLogger("elasticsearch.trace") if not _tracer_already_configured: tracer.propagate = False @@ -24,32 +29,53 @@ class Connection(object): 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 port: port to use (integer, default: 9200) :arg url_prefix: optional url prefix for elasticsearch :arg timeout: default timeout in seconds (float, default: 10) """ - scheme = kwargs.get('scheme', 'http') - if use_ssl or scheme == 'https': - scheme = 'https' + scheme = kwargs.get("scheme", "http") + if use_ssl or scheme == "https": + scheme = "https" use_ssl = True self.use_ssl = use_ssl - self.host = '%s://%s:%s' % (scheme, host, port) + self.host = "%s://%s:%s" % (scheme, host, port) if url_prefix: - url_prefix = '/' + url_prefix.strip('/') + url_prefix = "/" + url_prefix.strip("/") self.url_prefix = url_prefix self.timeout = timeout def __repr__(self): - return '<%s: %s>' % (self.__class__.__name__, self.host) + return "<%s: %s>" % (self.__class__.__name__, self.host) + + def __eq__(self, other): + if not isinstance(other, Connection): + raise TypeError( + "Unsupported equality check for %s and %s" % (self, other) + ) + return self.__hash__() == other.__hash__() + + def __hash__(self): + return id(self) def _pretty_json(self, data): # pretty JSON in tracer curl logs 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): # non-json data or a bulk request return data @@ -59,17 +85,28 @@ def _log_trace(self, method, path, body, status_code, response, duration): return # 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: - path = path.replace(self.url_prefix, '', 1) - tracer.info("curl %s-X%s 'http://localhost:9200%s' -d '%s'", - "-H 'Content-Type: application/json' " if body else '', - method, path, self._pretty_json(body) if body else '') + path = path.replace(self.url_prefix, "", 1) + tracer.info( + "curl %s-X%s 'http://localhost:9200%s' -d '%s'", + "-H 'Content-Type: application/json' " if body else "", + method, + path, + self._pretty_json(body) if body else "", + ) if tracer.isEnabledFor(logging.DEBUG): - 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): + 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 + ): """ Log a successful API call. """ # TODO: optionally pass in params instead of full_url and do urlencode only when needed @@ -77,43 +114,56 @@ def log_request_success(self, method, full_url, path, body, status_code, respons # TODO: find a better way to avoid (de)encoding the body back and forth if body: try: - body = body.decode('utf-8', 'ignore') + body = body.decode("utf-8", "ignore") except AttributeError: pass logger.info( - '%s %s [status:%s request:%.3fs]', method, full_url, - status_code, duration + "%s %s [status:%s request:%.3fs]", method, full_url, status_code, duration ) - logger.debug('> %s', body) - logger.debug('< %s', response) + logger.debug("> %s", body) + logger.debug("< %s", response) 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. """ # do not log 404s on HEAD requests - if method == 'HEAD' and status_code == 404: + if method == "HEAD" and status_code == 404: return logger.warning( - '%s %s [status:%s request:%.3fs]', method, full_url, - status_code or 'N/A', duration, exc_info=exception is not None + "%s %s [status:%s request:%.3fs]", + 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 # TODO: find a better way to avoid (de)encoding the body back and forth if body: try: - body = body.decode('utf-8', 'ignore') + body = body.decode("utf-8", "ignore") except AttributeError: pass - logger.debug('> %s', body) + logger.debug("> %s", body) self._log_trace(method, path, body, status_code, response, duration) if response is not None: - logger.debug('< %s', response) + logger.debug("< %s", response) def _raise_error(self, status_code, raw_data): """ Locate appropriate exception and raise it. """ @@ -122,12 +172,26 @@ def _raise_error(self, status_code, raw_data): try: if raw_data: additional_info = json.loads(raw_data) - error_message = additional_info.get('error', error_message) - if isinstance(error_message, dict) and 'type' in error_message: - error_message = error_message['type'] + error_message = additional_info.get("error", error_message) + if isinstance(error_message, dict) and "type" in error_message: + error_message = error_message["type"] 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 + ) + def _get_default_user_agent(self): + return "elasticsearch-py/%s (Python %s)" % (__versionstr__, python_version()) + def _get_api_key_header_val(self, api_key): + """ + Check the type of the passed api_key and return the correct header value + for the `API Key authentication ` + :arg api_key, either a tuple or a base64 encoded string + """ + if isinstance(api_key, (tuple, list)): + s = "{0}:{1}".format(api_key[0], api_key[1]).encode('utf-8') + return "ApiKey " + base64.b64encode(s).decode('utf-8') + return "ApiKey " + api_key diff --git a/elasticsearch/connection/http_requests.py b/elasticsearch/connection/http_requests.py index 1e590fbc3..e8fc22244 100644 --- a/elasticsearch/connection/http_requests.py +++ b/elasticsearch/connection/http_requests.py @@ -1,15 +1,24 @@ import time import warnings +from base64 import decodestring + try: import requests + REQUESTS_AVAILABLE = True except ImportError: REQUESTS_AVAILABLE = False from .base import Connection -from ..exceptions import ConnectionError, ImproperlyConfigured, ConnectionTimeout, SSLError +from ..exceptions import ( + ConnectionError, + ImproperlyConfigured, + ConnectionTimeout, + SSLError, +) from ..compat import urlencode, string_types + class RequestsHttpConnection(Connection): """ Connection using the `requests` library. @@ -26,26 +35,60 @@ class RequestsHttpConnection(Connection): :arg client_key: path to the file containing the private key if using separate cert and key files (client_cert will contain only the cert) :arg headers: any custom http headers to be add to requests + :arg cloud_id: The Cloud ID from ElasticCloud. Convient way to connect to cloud instances. + :arg api_key: optional API Key authentication as either base64 encoded string or a tuple. + Other host connection params will be ignored. """ - 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): + + 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, + cloud_id=None, + api_key=None, + **kwargs + ): if not REQUESTS_AVAILABLE: - raise ImproperlyConfigured("Please install requests to use RequestsHttpConnection.") + raise ImproperlyConfigured( + "Please install requests to use RequestsHttpConnection." + ) + if cloud_id: + cluster_name, cloud_id = cloud_id.split(":") + url, es_uuid, kibana_uuid = ( + decodestring(cloud_id.encode("utf-8")).decode("utf-8").split("$") + ) + host = "%s.%s" % (es_uuid, url) + port = 9243 + use_ssl = True - super(RequestsHttpConnection, self).__init__(host=host, port=port, use_ssl=use_ssl, **kwargs) + super(RequestsHttpConnection, self).__init__( + host=host, port=port, use_ssl=use_ssl, **kwargs + ) self.session = requests.Session() self.session.headers = headers or {} - self.session.headers.setdefault('content-type', 'application/json') + self.session.headers.setdefault("content-type", "application/json") + self.session.headers.setdefault("user-agent", self._get_default_user_agent()) if http_auth is not None: if isinstance(http_auth, (tuple, list)): http_auth = tuple(http_auth) 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.base_url = 'http%s://%s:%d%s' % ( - 's' if self.use_ssl else '', - host, port, self.url_prefix + if api_key is not None: + self.session.headers['authorization'] = self._get_api_key_header_val(api_key) + self.base_url = "http%s://%s:%d%s" % ( + "s" if self.use_ssl else "", + host, + port, + self.url_prefix, ) self.session.verify = verify_certs if not client_key: @@ -55,42 +98,76 @@ def __init__(self, host='localhost', port=9200, http_auth=None, self.session.cert = (client_cert, client_key) if ca_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 if self.use_ssl and not verify_certs and ssl_show_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 if params: - url = '%s?%s' % (url, urlencode(params or {})) + url = "%s?%s" % (url, urlencode(params or {})) start = time.time() request = requests.Request(method=method, headers=headers, url=url, data=body) prepared_request = self.session.prepare_request(request) - settings = self.session.merge_environment_settings(prepared_request.url, {}, None, None, None) - send_kwargs = {'timeout': timeout or self.timeout} + settings = self.session.merge_environment_settings( + prepared_request.url, {}, None, None, None + ) + send_kwargs = {"timeout": timeout or self.timeout} send_kwargs.update(settings) try: response = self.session.send(prepared_request, **send_kwargs) duration = time.time() - start raw_data = response.text 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): - raise SSLError('N/A', str(e), e) + raise SSLError("N/A", str(e), e) if isinstance(e, requests.Timeout): - raise ConnectionTimeout('TIMEOUT', str(e), e) - raise ConnectionError('N/A', str(e), e) + raise ConnectionTimeout("TIMEOUT", str(e), e) + raise ConnectionError("N/A", str(e), e) # 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: - self.log_request_fail(method, url, response.request.path_url, body, duration, response.status_code, raw_data) + if ( + 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.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 diff --git a/elasticsearch/connection/http_urllib3.py b/elasticsearch/connection/http_urllib3.py index d573b0b9a..9f112fa2d 100644 --- a/elasticsearch/connection/http_urllib3.py +++ b/elasticsearch/connection/http_urllib3.py @@ -5,6 +5,7 @@ from urllib3.util.retry import Retry import warnings import gzip +from base64 import decodestring # sentinal value for `verify_certs`. # This is used to detect if a user is passing in a value for `verify_certs` @@ -72,6 +73,9 @@ class Urllib3HttpConnection(Connection): information. :arg headers: any custom http headers to be add to requests :arg http_compress: Use gzip compression + :arg cloud_id: The Cloud ID from ElasticCloud. Convient way to connect to cloud instances. + :arg api_key: optional API Key authentication as either base64 encoded string or a tuple. + Other host connection params will be ignored. """ def __init__( @@ -92,9 +96,19 @@ def __init__( headers=None, ssl_context=None, http_compress=False, + cloud_id=None, + api_key=None, **kwargs ): + if cloud_id: + cluster_name, cloud_id = cloud_id.split(":") + url, es_uuid, kibana_uuid = ( + decodestring(cloud_id.encode("utf-8")).decode("utf-8").split("$") + ) + host = "%s.%s" % (es_uuid, url) + port = "9243" + use_ssl = True super(Urllib3HttpConnection, self).__init__( host=host, port=port, use_ssl=use_ssl, **kwargs ) @@ -115,6 +129,9 @@ def __init__( self.headers.update({"content-encoding": "gzip"}) self.headers.setdefault("content-type", "application/json") + self.headers.setdefault("user-agent", self._get_default_user_agent()) + if api_key is not None: + self.headers.setdefault('authorization', self._get_api_key_header_val(api_key)) pool_class = urllib3.HTTPConnectionPool kw = {} @@ -172,6 +189,7 @@ def __init__( } ) else: + kw["cert_reqs"] = "CERT_NONE" if ssl_show_warn: warnings.warn( "Connecting to %s using SSL with verify_certs=False is insecure." diff --git a/elasticsearch/connection/pooling.py b/elasticsearch/connection/pooling.py index 3115e7c2c..dd5431e15 100644 --- a/elasticsearch/connection/pooling.py +++ b/elasticsearch/connection/pooling.py @@ -12,6 +12,7 @@ class PoolingConnection(Connection): ``_make_connection`` method that constructs a new connection and returns it. """ + def __init__(self, *args, **kwargs): self._free_connections = queue.Queue() super(PoolingConnection, self).__init__(*args, **kwargs) @@ -30,4 +31,3 @@ def close(self): Explicitly close connection """ pass - diff --git a/elasticsearch/connection_pool.py b/elasticsearch/connection_pool.py index c28bb3ccc..d9bdcd293 100644 --- a/elasticsearch/connection_pool.py +++ b/elasticsearch/connection_pool.py @@ -10,7 +10,8 @@ from .exceptions import ImproperlyConfigured -logger = logging.getLogger('elasticsearch') +logger = logging.getLogger("elasticsearch") + class ConnectionSelector(object): """ @@ -30,6 +31,7 @@ class ConnectionSelector(object): only select connections from it's own zones and only fall back to other connections where there would be none in it's zones. """ + def __init__(self, opts): """ :arg opts: dictionary of connection instances and their options @@ -49,6 +51,7 @@ class RandomSelector(ConnectionSelector): """ Select a connection at random """ + def select(self, connections): return random.choice(connections) @@ -57,15 +60,17 @@ class RoundRobinSelector(ConnectionSelector): """ Selector using round-robin. """ + def __init__(self, opts): super(RoundRobinSelector, self).__init__(opts) self.data = threading.local() 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) return connections[self.data.rr] + class ConnectionPool(object): """ 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 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 :class:`~elasticsearch.Connection` instance and it's options @@ -103,8 +116,9 @@ def __init__(self, connections, dead_timeout=60, timeout_cutoff=5, avoid dog piling effect across processes """ if not connections: - raise ImproperlyConfigured("No defined connections, you need to " - "specify at least one host.") + raise ImproperlyConfigured( + "No defined connections, you need to " "specify at least one host." + ) self.connection_opts = connections self.connections = [c for (c, opts) in connections] # remember original connection list for resurrect(force=True) @@ -136,6 +150,7 @@ def mark_dead(self, connection, now=None): try: self.connections.remove(connection) except ValueError: + logger.info("Attempted to remove %r, but it does not exist in the connection pool.", connection) # connection not alive or another thread marked it already, ignore return else: @@ -144,8 +159,10 @@ def mark_dead(self, connection, now=None): timeout = self.dead_timeout * 2 ** min(dead_count - 1, self.timeout_cutoff) self.dead.put((now + timeout, connection)) logger.warning( - 'Connection %r has failed for %i times in a row, putting on %i second timeout.', - connection, dead_count, timeout + "Connection %r has failed for %i times in a row, putting on %i second timeout.", + connection, + dead_count, + timeout, ) def mark_live(self, connection): @@ -200,7 +217,7 @@ def resurrect(self, force=False): # either we were forced or the connection is elligible to be retried self.connections.append(connection) - logger.info('Resurrecting connection %r (force=%s).', connection, force) + logger.info("Resurrecting connection %r (force=%s).", connection, force) return connection def get_connection(self): @@ -235,15 +252,17 @@ def close(self): for conn in self.orig_connections: conn.close() + class DummyConnectionPool(ConnectionPool): def __init__(self, connections, **kwargs): if len(connections) != 1: - raise ImproperlyConfigured("DummyConnectionPool needs exactly one " - "connection defined.") + raise ImproperlyConfigured( + "DummyConnectionPool needs exactly one " "connection defined." + ) # we need connection opts for sniffing logic self.connection_opts = connections self.connection = connections[0][0] - self.connections = (self.connection, ) + self.connections = (self.connection,) def get_connection(self): return self.connection @@ -256,6 +275,5 @@ def close(self): def _noop(self, *args, **kwargs): pass - mark_dead = mark_live = resurrect = _noop - + mark_dead = mark_live = resurrect = _noop diff --git a/elasticsearch/exceptions.py b/elasticsearch/exceptions.py index 00f1d01ab..e5bfc5c68 100644 --- a/elasticsearch/exceptions.py +++ b/elasticsearch/exceptions.py @@ -1,7 +1,16 @@ __all__ = [ - 'ImproperlyConfigured', 'ElasticsearchException', 'SerializationError', - 'TransportError', 'NotFoundError', 'ConflictError', 'RequestError', 'ConnectionError', - 'SSLError', 'ConnectionTimeout', 'AuthenticationException', 'AuthorizationException' + "ImproperlyConfigured", + "ElasticsearchException", + "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 be set to ``'N/A'``. """ + @property def status_code(self): """ @@ -53,32 +63,43 @@ def info(self): return self.args[2] def __str__(self): - cause = '' + cause = "" try: - if self.info and 'error' in self.info: - if isinstance(self.info['error'], dict): - root_cause = self.info['error']['root_cause'][0] - cause = ', '.join(filter(None, [repr(root_cause['reason']), root_cause.get('resource.id'), - root_cause.get('resource.type')])) + if self.info and "error" in self.info: + if isinstance(self.info["error"], dict): + root_cause = self.info["error"]["root_cause"][0] + cause = ", ".join( + filter( + None, + [ + repr(root_cause["reason"]), + root_cause.get("resource.id"), + root_cause.get("resource.type"), + ], + ) + ) else: - cause = repr(self.info['error']) + cause = repr(self.info["error"]) except LookupError: pass - msg = ', '.join(filter(None, [str(self.status_code), repr(self.error), cause])) - return '%s(%s)' % (self.__class__.__name__, msg) + msg = ", ".join(filter(None, [str(self.status_code), repr(self.error), cause])) + return "%s(%s)" % (self.__class__.__name__, msg) class ConnectionError(TransportError): """ Error raised when there was an exception while talking to ES. Original exception from the underlying :class:`~elasticsearch.Connection` - implementation is available as ``.info.`` + implementation is available as ``.info``. """ def __str__(self): - return 'ConnectionError(%s) caused by: %s(%s)' % ( - self.error, self.info.__class__.__name__, self.info) + return "ConnectionError(%s) caused by: %s(%s)" % ( + self.error, + self.info.__class__.__name__, + self.info, + ) class SSLError(ConnectionError): @@ -89,8 +110,10 @@ class ConnectionTimeout(ConnectionError): """ A network timeout. Doesn't cause a node retry by default. """ def __str__(self): - return 'ConnectionTimeout caused by - %s(%s)' % ( - self.info.__class__.__name__, self.info) + return "ConnectionTimeout caused by - %s(%s)" % ( + self.info.__class__.__name__, + self.info, + ) class NotFoundError(TransportError): diff --git a/elasticsearch/helpers/__init__.py b/elasticsearch/helpers/__init__.py index c5c81be66..848138bf0 100644 --- a/elasticsearch/helpers/__init__.py +++ b/elasticsearch/helpers/__init__.py @@ -1,8 +1,4 @@ - - from .errors import BulkIndexError, ScanError from .actions import expand_action, streaming_bulk, bulk, parallel_bulk from .actions import scan, reindex from .actions import _chunk_actions, _process_bulk_chunk - - diff --git a/elasticsearch/helpers/actions.py b/elasticsearch/helpers/actions.py index bfdf64187..2b5a630d4 100644 --- a/elasticsearch/helpers/actions.py +++ b/elasticsearch/helpers/actions.py @@ -143,15 +143,17 @@ def _process_bulk_chunk( bulk_data, map(methodcaller("popitem"), resp["items"]) ): ok = 200 <= item.get("status", 500) < 300 - if not ok and raise_on_error: + if not ok: # include original document source - if len(data) > 1: + if op_type != "delete": item["data"] = data[1] - errors.append({op_type: item}) - - if ok or not errors: - # if we are not just recording all errors to be able to raise - # them all at once, yield items individually + if raise_on_error: + errors.append({op_type: item}) + else: + # if we are not just recording all errors to be able to raise + # them all at once, yield items individually + yield ok, {op_type: item} + else: yield ok, {op_type: item} if errors: @@ -347,7 +349,7 @@ def parallel_bulk( class BlockingPool(ThreadPool): def _setup_queues(self): super(BlockingPool, self)._setup_queues() - # The queue must be at least the size of the number of threads to + # The queue must be at least the size of the number of threads to # prevent hanging when inserting sentinel values during teardown. self._inqueue = Queue(max(queue_size, thread_count)) self._quick_put = self._inqueue.put @@ -437,29 +439,26 @@ def scan( scroll_id = resp.get("_scroll_id") try: - while scroll_id and resp['hits']['hits']: + while scroll_id and resp["hits"]["hits"]: for hit in resp["hits"]["hits"]: yield hit # check if we have any errors - if resp["_shards"]["successful"] < resp["_shards"]["total"]: + if (resp["_shards"]["successful"] + resp["_shards"]["skipped"]) < resp["_shards"]["total"]: logger.warning( - "Scroll request has only succeeded on %d shards out of %d.", + "Scroll request has only succeeded on %d (+%d skipped) shards out of %d.", resp["_shards"]["successful"], + resp["_shards"]["skipped"], resp["_shards"]["total"], ) if raise_on_error: raise ScanError( scroll_id, - "Scroll request has only succeeded on %d shards out of %d." - % (resp["_shards"]["successful"], resp["_shards"]["total"]), + "Scroll request has only succeeded on %d (+%d skiped) shards out of %d." + % (resp["_shards"]["successful"], resp["_shards"]["skipped"], resp["_shards"]["total"]), ) - resp = client.scroll( - scroll_id, - scroll=scroll, - request_timeout=request_timeout, - **scroll_kwargs + body={"scroll_id": scroll_id, "scroll": scroll}, **scroll_kwargs ) scroll_id = resp.get("_scroll_id") diff --git a/elasticsearch/helpers/errors.py b/elasticsearch/helpers/errors.py index 5f4dc7485..6261822e5 100644 --- a/elasticsearch/helpers/errors.py +++ b/elasticsearch/helpers/errors.py @@ -1,5 +1,3 @@ - - from ..exceptions import ElasticsearchException diff --git a/elasticsearch/helpers/test.py b/elasticsearch/helpers/test.py index 6fdd2ec05..cdc7a981c 100644 --- a/elasticsearch/helpers/test.py +++ b/elasticsearch/helpers/test.py @@ -1,5 +1,6 @@ import time import os + try: # python 2.6 from unittest2 import TestCase, SkipTest @@ -9,33 +10,37 @@ from elasticsearch import Elasticsearch from elasticsearch.exceptions import ConnectionError + def get_test_client(nowait=False, **kwargs): # construct kwargs from the environment - kw = {'timeout': 30} - if 'TEST_ES_CONNECTION' in os.environ: + kw = {"timeout": 30} + if "TEST_ES_CONNECTION" in os.environ: 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) - client = Elasticsearch([os.environ.get('TEST_ES_SERVER', {})], **kw) + client = Elasticsearch([os.environ.get("TEST_ES_SERVER", {})], **kw) # wait for yellow status for _ in range(1 if nowait else 100): try: - client.cluster.health(wait_for_status='yellow') + client.cluster.health(wait_for_status="yellow") return client except ConnectionError: - time.sleep(.1) + time.sleep(0.1) else: # timeout raise SkipTest("Elasticsearch failed to start.") + def _get_version(version_string): - if '.' not in version_string: + if "." not in version_string: return () - version = version_string.strip().split('.') + version = version_string.strip().split(".") return tuple(int(v) if v.isdigit() else 999 for v in version) + class ElasticsearchTestCase(TestCase): @staticmethod def _get_client(): @@ -48,13 +53,12 @@ def setUpClass(cls): def tearDown(self): super(ElasticsearchTestCase, self).tearDown() - self.client.indices.delete(index='*', ignore=404) - self.client.indices.delete_template(name='*', ignore=404) + self.client.indices.delete(index="*", ignore=404) + self.client.indices.delete_template(name="*", ignore=404) @property def es_version(self): - if not hasattr(self, '_es_version'): - version_string = self.client.info()['version']['number'] + if not hasattr(self, "_es_version"): + version_string = self.client.info()["version"]["number"] self._es_version = _get_version(version_string) return self._es_version - diff --git a/elasticsearch/serializer.py b/elasticsearch/serializer.py index 12f63f2a3..dd7d0dc87 100644 --- a/elasticsearch/serializer.py +++ b/elasticsearch/serializer.py @@ -9,8 +9,9 @@ from .exceptions import SerializationError, ImproperlyConfigured from .compat import string_types + class TextSerializer(object): - mimetype = 'text/plain' + mimetype = "text/plain" def loads(self, s): return s @@ -19,10 +20,11 @@ def dumps(self, data): if isinstance(data, string_types): return data - raise SerializationError('Cannot serialize %r into text.' % data) + raise SerializationError("Cannot serialize %r into text." % data) + class JSONSerializer(object): - mimetype = 'application/json' + mimetype = "application/json" def default(self, data): if isinstance(data, (date, datetime)): @@ -46,25 +48,26 @@ def dumps(self, data): try: return json.dumps( - data, - default=self.default, - ensure_ascii=False, - separators=(',', ':'), + data, default=self.default, ensure_ascii=False, separators=(",", ":") ) except (ValueError, TypeError) as e: raise SerializationError(data, e) + DEFAULT_SERIALIZERS = { JSONSerializer.mimetype: JSONSerializer(), TextSerializer.mimetype: TextSerializer(), } + class Deserializer(object): - def __init__(self, serializers, default_mimetype='application/json'): + def __init__(self, serializers, default_mimetype="application/json"): try: self.default = serializers[default_mimetype] except KeyError: - raise ImproperlyConfigured('Cannot find default serializer (%s)' % default_mimetype) + raise ImproperlyConfigured( + "Cannot find default serializer (%s)" % default_mimetype + ) self.serializers = serializers def loads(self, s, mimetype=None): @@ -72,11 +75,12 @@ def loads(self, s, mimetype=None): deserializer = self.default else: # split out charset - mimetype, _, _ = mimetype.partition(';') + mimetype, _, _ = mimetype.partition(";") try: deserializer = self.serializers[mimetype] except KeyError: - raise SerializationError('Unknown mimetype, unable to deserialize: %s' % mimetype) + raise SerializationError( + "Unknown mimetype, unable to deserialize: %s" % mimetype + ) return deserializer.loads(s) - diff --git a/elasticsearch/transport.py b/elasticsearch/transport.py index b7801bc56..02fe79bfd 100644 --- a/elasticsearch/transport.py +++ b/elasticsearch/transport.py @@ -1,11 +1,17 @@ import time from itertools import chain +from platform import python_version from .connection import Urllib3HttpConnection from .connection_pool import ConnectionPool, DummyConnectionPool from .serializer import JSONSerializer, Deserializer, DEFAULT_SERIALIZERS -from .exceptions import ConnectionError, TransportError, SerializationError, \ - ConnectionTimeout +from . import __versionstr__ +from .exceptions import ( + ConnectionError, + TransportError, + SerializationError, + ConnectionTimeout, +) def get_host_info(node_info, host): @@ -23,10 +29,11 @@ def get_host_info(node_info, host): :arg host: connection information (host, port) extracted from the node info """ # ignore master only nodes - if node_info.get('roles', []) == ['master']: + if node_info.get("roles", []) == ["master"]: return None return host + class Transport(object): """ Encapsulation of transport-related to logic. Handles instantiation of the @@ -34,22 +41,36 @@ class Transport(object): Main interface is the `perform_request` method. """ - def __init__(self, hosts, connection_class=Urllib3HttpConnection, - connection_pool_class=ConnectionPool, host_info_callback=get_host_info, - sniff_on_start=False, sniffer_timeout=None, sniff_timeout=.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): + + def __init__( + self, + hosts, + connection_class=Urllib3HttpConnection, + 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 create a `connection_class` instance :arg connection_class: subclass of :class:`~elasticsearch.Connection` to use :arg connection_pool_class: subclass of :class:`~elasticsearch.ConnectionPool` to use :arg host_info_callback: callback responsible for taking the node information from - `/_cluser/nodes`, along with already extracted information, and + `/_cluster/nodes`, along with already extracted information, and producing a list of arguments (same as `hosts` parameter) :arg sniff_on_start: flag indicating whether to obtain a list of nodes - from the cluser at startup time + from the cluster at startup time :arg sniffer_timeout: number of seconds between automatic sniffs :arg sniff_on_connection_fail: flag controlling if connection failure triggers a sniff :arg sniff_timeout: timeout used for the sniff request - it should be a @@ -143,7 +164,7 @@ def _create_connection(host): # if this is not the initial setup look at the existing connection # options and identify connections that haven't changed and can be # kept around. - if hasattr(self, 'connection_pool'): + if hasattr(self, "connection_pool"): for (connection, old_host) in self.connection_pool.connection_opts: if old_host == host: return connection @@ -152,6 +173,7 @@ def _create_connection(host): kwargs = self.kwargs.copy() kwargs.update(host) return self.connection_class(**kwargs) + connections = map(_create_connection, hosts) connections = list(zip(connections, hosts)) @@ -159,11 +181,13 @@ def _create_connection(host): self.connection_pool = DummyConnectionPool(connections) else: # 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): """ - Retreive a :class:`~elasticsearch.Connection` instance from the + Retrieve a :class:`~elasticsearch.Connection` instance from the :class:`~elasticsearch.ConnectionPool` instance. """ if self.sniffer_timeout: @@ -173,7 +197,7 @@ def get_connection(self): def _get_sniff_data(self, initial=False): """ - Perform the request to get sniffins information. Returns a list of + Perform the request to get sniffing information. Returns a list of dictionaries (one per node) containing all the information from the cluster. @@ -194,9 +218,13 @@ def _get_sniff_data(self, initial=False): try: # use small timeout for the sniffing request, should be a fast api call _, headers, node_info = c.perform_request( - 'GET', '/_nodes/_all/http', - timeout=self.sniff_timeout if not initial else None) - node_info = self.deserializer.loads(node_info, headers.get('content-type')) + "GET", + "/_nodes/_all/http", + timeout=self.sniff_timeout if not initial else None, + ) + node_info = self.deserializer.loads( + node_info, headers.get("content-type") + ) break except (ConnectionError, SerializationError): pass @@ -207,18 +235,26 @@ def _get_sniff_data(self, initial=False): self.last_sniff = previous_sniff raise - return list(node_info['nodes'].values()) + return list(node_info["nodes"].values()) def _get_host_info(self, host_info): host = {} - address = host_info.get('http', {}).get('publish_address') + address = host_info.get("http", {}).get("publish_address") # malformed or no address given - if not address or ':' not in address: + if not address or ":" not in address: return None - host['host'], host['port'] = address.rsplit(':', 1) - host['port'] = int(host['port']) + if '/' in address: + # Support 7.x host/ip:port behavior where http.publish_host has been set. + fqdn, ipaddress = address.split('/', 1) + host["host"] = fqdn + _, host["port"] = ipaddress.rsplit(':', 1) + host["port"] = int(host["port"]) + + else: + host["host"], host["port"] = address.rsplit(":", 1) + host["port"] = int(host["port"]) return self.host_info_callback(host_info, host) @@ -239,7 +275,9 @@ def sniff_hosts(self, initial=False): # we weren't able to get any nodes or host_info_callback blocked all - # raise error. 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) @@ -264,7 +302,7 @@ def perform_request(self, method, url, headers=None, params=None, body=None): If an exception was raised, mark the connection as failed and retry (up to `max_retries` times). - If the operation was succesful and the connection used was previously + If the operation was successful and the connection used was previously marked as dead, mark it as live, resetting it's failure count. :arg method: HTTP method to use @@ -273,28 +311,28 @@ def perform_request(self, method, url, headers=None, params=None, body=None): underlying :class:`~elasticsearch.Connection` class :arg params: dictionary of query parameters, will be handed over to the underlying :class:`~elasticsearch.Connection` class for serialization - :arg body: body of the request, will be serializes using serializer and + :arg body: body of the request, will be serialized using serializer and passed to the connection """ if body is not None: body = self.serializer.dumps(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 - if self.send_get_body_as == 'POST': - method = 'POST' + if self.send_get_body_as == "POST": + method = "POST" # or as source parameter - elif self.send_get_body_as == 'source': + elif self.send_get_body_as == "source": if params is None: params = {} - params['source'] = body + params["source"] = body body = None if body is not None: try: - body = body.encode('utf-8', 'surrogatepass') + body = body.encode("utf-8", "surrogatepass") except (UnicodeDecodeError, AttributeError): # bytes/str - no need to re-encode pass @@ -302,23 +340,26 @@ def perform_request(self, method, url, headers=None, params=None, body=None): ignore = () timeout = None if params: - timeout = params.pop('request_timeout', None) - ignore = params.pop('ignore', ()) + timeout = params.pop("request_timeout", None) + ignore = params.pop("ignore", ()) if isinstance(ignore, int): - ignore = (ignore, ) - + ignore = (ignore,) for attempt in range(self.max_retries + 1): connection = self.get_connection() try: - # add a delay before attempting the next retry - # 0, 1, 3, 7, etc... - delay = 2**attempt - 1 - 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: - if method == 'HEAD' and e.status_code == 404: + if method == "HEAD" and e.status_code == 404: return False retry = False @@ -342,11 +383,13 @@ def perform_request(self, method, url, headers=None, params=None, body=None): # connection didn't fail, confirm it's live status self.connection_pool.mark_live(connection) - if method == 'HEAD': + if method == "HEAD": return 200 <= status < 300 if data: - data = self.deserializer.loads(data, headers_response.get('content-type')) + data = self.deserializer.loads( + data, headers_response.get("content-type") + ) return data def close(self): diff --git a/example/docs/asciidoc/.gitignore b/example/docs/asciidoc/.gitignore new file mode 100644 index 000000000..5348786e5 --- /dev/null +++ b/example/docs/asciidoc/.gitignore @@ -0,0 +1,2 @@ +_*.asciidoc +_*.adoc \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Delete/47b5ff897f26e9c943cee5c06034181d.adoc b/example/docs/asciidoc/Docs/Delete/47b5ff897f26e9c943cee5c06034181d.adoc new file mode 100644 index 000000000..0465dbcca --- /dev/null +++ b/example/docs/asciidoc/Docs/Delete/47b5ff897f26e9c943cee5c06034181d.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Delete.py[tag=47b5ff897f26e9c943cee5c06034181d,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Delete/c5e5873783246c7b1c01d8464fed72c4.adoc b/example/docs/asciidoc/Docs/Delete/c5e5873783246c7b1c01d8464fed72c4.adoc new file mode 100644 index 000000000..e81da6243 --- /dev/null +++ b/example/docs/asciidoc/Docs/Delete/c5e5873783246c7b1c01d8464fed72c4.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Delete.py[tag=c5e5873783246c7b1c01d8464fed72c4,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Delete/d90a84a24a407731dfc1929ac8327746.adoc b/example/docs/asciidoc/Docs/Delete/d90a84a24a407731dfc1929ac8327746.adoc new file mode 100644 index 000000000..d77a7c87c --- /dev/null +++ b/example/docs/asciidoc/Docs/Delete/d90a84a24a407731dfc1929ac8327746.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Delete.py[tag=d90a84a24a407731dfc1929ac8327746,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Get/0ba0b2db24852abccb7c0fc1098d566e.adoc b/example/docs/asciidoc/Docs/Get/0ba0b2db24852abccb7c0fc1098d566e.adoc new file mode 100644 index 000000000..9bdd145ca --- /dev/null +++ b/example/docs/asciidoc/Docs/Get/0ba0b2db24852abccb7c0fc1098d566e.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Get.py[tag=0ba0b2db24852abccb7c0fc1098d566e,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Get/138ccd89f72aa7502dd9578403dcc589.adoc b/example/docs/asciidoc/Docs/Get/138ccd89f72aa7502dd9578403dcc589.adoc new file mode 100644 index 000000000..e40e1f387 --- /dev/null +++ b/example/docs/asciidoc/Docs/Get/138ccd89f72aa7502dd9578403dcc589.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Get.py[tag=138ccd89f72aa7502dd9578403dcc589,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Get/1d65cb6d055c46a1bde809687d835b71.adoc b/example/docs/asciidoc/Docs/Get/1d65cb6d055c46a1bde809687d835b71.adoc new file mode 100644 index 000000000..6e2b1d2bf --- /dev/null +++ b/example/docs/asciidoc/Docs/Get/1d65cb6d055c46a1bde809687d835b71.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Get.py[tag=1d65cb6d055c46a1bde809687d835b71,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Get/5eabcdbf61bfcb484dc694f25c2bba36.adoc b/example/docs/asciidoc/Docs/Get/5eabcdbf61bfcb484dc694f25c2bba36.adoc new file mode 100644 index 000000000..74b1b5c0d --- /dev/null +++ b/example/docs/asciidoc/Docs/Get/5eabcdbf61bfcb484dc694f25c2bba36.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Get.py[tag=5eabcdbf61bfcb484dc694f25c2bba36,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Get/69a7be47f85138b10437113ab2f0d72d.adoc b/example/docs/asciidoc/Docs/Get/69a7be47f85138b10437113ab2f0d72d.adoc new file mode 100644 index 000000000..c699f1ac7 --- /dev/null +++ b/example/docs/asciidoc/Docs/Get/69a7be47f85138b10437113ab2f0d72d.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Get.py[tag=69a7be47f85138b10437113ab2f0d72d,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Get/710c7871f20f176d51209b1574b0d61b.adoc b/example/docs/asciidoc/Docs/Get/710c7871f20f176d51209b1574b0d61b.adoc new file mode 100644 index 000000000..603e8e5a4 --- /dev/null +++ b/example/docs/asciidoc/Docs/Get/710c7871f20f176d51209b1574b0d61b.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Get.py[tag=710c7871f20f176d51209b1574b0d61b,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Get/745f9b8cdb8e91073f6e520e1d9f8c05.adoc b/example/docs/asciidoc/Docs/Get/745f9b8cdb8e91073f6e520e1d9f8c05.adoc new file mode 100644 index 000000000..47555b5e9 --- /dev/null +++ b/example/docs/asciidoc/Docs/Get/745f9b8cdb8e91073f6e520e1d9f8c05.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Get.py[tag=745f9b8cdb8e91073f6e520e1d9f8c05,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Get/89a8ac1509936acc272fc2d72907bc45.adoc b/example/docs/asciidoc/Docs/Get/89a8ac1509936acc272fc2d72907bc45.adoc new file mode 100644 index 000000000..db6d96285 --- /dev/null +++ b/example/docs/asciidoc/Docs/Get/89a8ac1509936acc272fc2d72907bc45.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Get.py[tag=89a8ac1509936acc272fc2d72907bc45,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Get/8fdf2344c4fb3de6902ad7c5735270df.adoc b/example/docs/asciidoc/Docs/Get/8fdf2344c4fb3de6902ad7c5735270df.adoc new file mode 100644 index 000000000..9f0877512 --- /dev/null +++ b/example/docs/asciidoc/Docs/Get/8fdf2344c4fb3de6902ad7c5735270df.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Get.py[tag=8fdf2344c4fb3de6902ad7c5735270df,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Get/98234499cfec70487cec5d013e976a84.adoc b/example/docs/asciidoc/Docs/Get/98234499cfec70487cec5d013e976a84.adoc new file mode 100644 index 000000000..aadedb4fa --- /dev/null +++ b/example/docs/asciidoc/Docs/Get/98234499cfec70487cec5d013e976a84.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Get.py[tag=98234499cfec70487cec5d013e976a84,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Get/d222c6a6ec7a3beca6c97011b0874512.adoc b/example/docs/asciidoc/Docs/Get/d222c6a6ec7a3beca6c97011b0874512.adoc new file mode 100644 index 000000000..f826b3df3 --- /dev/null +++ b/example/docs/asciidoc/Docs/Get/d222c6a6ec7a3beca6c97011b0874512.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Get.py[tag=d222c6a6ec7a3beca6c97011b0874512,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Get/fbcf5078a6a9e09790553804054c36b3.adoc b/example/docs/asciidoc/Docs/Get/fbcf5078a6a9e09790553804054c36b3.adoc new file mode 100644 index 000000000..7b1082391 --- /dev/null +++ b/example/docs/asciidoc/Docs/Get/fbcf5078a6a9e09790553804054c36b3.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Get.py[tag=fbcf5078a6a9e09790553804054c36b3,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Index/048d8abd42d094bbdcf4452a58ccb35b.adoc b/example/docs/asciidoc/Docs/Index/048d8abd42d094bbdcf4452a58ccb35b.adoc new file mode 100644 index 000000000..a700cb942 --- /dev/null +++ b/example/docs/asciidoc/Docs/Index/048d8abd42d094bbdcf4452a58ccb35b.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Index.py[tag=048d8abd42d094bbdcf4452a58ccb35b,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Index/1f336ecc62480c1d56351cc2f82d0d08.adoc b/example/docs/asciidoc/Docs/Index/1f336ecc62480c1d56351cc2f82d0d08.adoc new file mode 100644 index 000000000..dcbc8e539 --- /dev/null +++ b/example/docs/asciidoc/Docs/Index/1f336ecc62480c1d56351cc2f82d0d08.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Index.py[tag=1f336ecc62480c1d56351cc2f82d0d08,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Index/36818c6d9f434d387819c30bd9addb14.adoc b/example/docs/asciidoc/Docs/Index/36818c6d9f434d387819c30bd9addb14.adoc new file mode 100644 index 000000000..ed31deb28 --- /dev/null +++ b/example/docs/asciidoc/Docs/Index/36818c6d9f434d387819c30bd9addb14.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Index.py[tag=36818c6d9f434d387819c30bd9addb14,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Index/625dc94df1f9affb49a082fd99d41620.adoc b/example/docs/asciidoc/Docs/Index/625dc94df1f9affb49a082fd99d41620.adoc new file mode 100644 index 000000000..23003ba80 --- /dev/null +++ b/example/docs/asciidoc/Docs/Index/625dc94df1f9affb49a082fd99d41620.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Index.py[tag=625dc94df1f9affb49a082fd99d41620,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Index/b918d6b798da673a33e49b94f61dcdc0.adoc b/example/docs/asciidoc/Docs/Index/b918d6b798da673a33e49b94f61dcdc0.adoc new file mode 100644 index 000000000..2a67b8f75 --- /dev/null +++ b/example/docs/asciidoc/Docs/Index/b918d6b798da673a33e49b94f61dcdc0.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Index.py[tag=b918d6b798da673a33e49b94f61dcdc0,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Index/bb143628fd04070683eeeadc9406d9cc.adoc b/example/docs/asciidoc/Docs/Index/bb143628fd04070683eeeadc9406d9cc.adoc new file mode 100644 index 000000000..c2c1de0a0 --- /dev/null +++ b/example/docs/asciidoc/Docs/Index/bb143628fd04070683eeeadc9406d9cc.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Index.py[tag=bb143628fd04070683eeeadc9406d9cc,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/Docs/Index/d718b63cf1b6591a1d59a0cf4fd995eb.adoc b/example/docs/asciidoc/Docs/Index/d718b63cf1b6591a1d59a0cf4fd995eb.adoc new file mode 100644 index 000000000..636738f05 --- /dev/null +++ b/example/docs/asciidoc/Docs/Index/d718b63cf1b6591a1d59a0cf4fd995eb.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../src/Examples/Docs/Docs/Index.py[tag=d718b63cf1b6591a1d59a0cf4fd995eb,indent=0] +---- \ No newline at end of file diff --git a/example/docs/asciidoc/GettingStarted/0caf6b6b092ecbcf6f996053678a2384.adoc b/example/docs/asciidoc/GettingStarted/0caf6b6b092ecbcf6f996053678a2384.adoc new file mode 100644 index 000000000..b453450a9 --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/0caf6b6b092ecbcf6f996053678a2384.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=0caf6b6b092ecbcf6f996053678a2384,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/193864342d9f0a36ec84a91ca325f5ec.adoc b/example/docs/asciidoc/GettingStarted/193864342d9f0a36ec84a91ca325f5ec.adoc new file mode 100644 index 000000000..ec667501c --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/193864342d9f0a36ec84a91ca325f5ec.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=193864342d9f0a36ec84a91ca325f5ec,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/1c0f3b0bc4b7e53b53755fd3bda5b8cf.adoc b/example/docs/asciidoc/GettingStarted/1c0f3b0bc4b7e53b53755fd3bda5b8cf.adoc new file mode 100644 index 000000000..30df8ac5c --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/1c0f3b0bc4b7e53b53755fd3bda5b8cf.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=1c0f3b0bc4b7e53b53755fd3bda5b8cf,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/21fe98843fe0b5473f515d21803db409.adoc b/example/docs/asciidoc/GettingStarted/21fe98843fe0b5473f515d21803db409.adoc new file mode 100644 index 000000000..c4068cf7b --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/21fe98843fe0b5473f515d21803db409.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=21fe98843fe0b5473f515d21803db409,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/345ea7e9cb5af9e052ce0cf6f1f52c23.adoc b/example/docs/asciidoc/GettingStarted/345ea7e9cb5af9e052ce0cf6f1f52c23.adoc new file mode 100644 index 000000000..a268cc377 --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/345ea7e9cb5af9e052ce0cf6f1f52c23.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=345ea7e9cb5af9e052ce0cf6f1f52c23,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/37a3b66b555aed55618254f50d572cd6.adoc b/example/docs/asciidoc/GettingStarted/37a3b66b555aed55618254f50d572cd6.adoc new file mode 100644 index 000000000..3d3b7637a --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/37a3b66b555aed55618254f50d572cd6.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=37a3b66b555aed55618254f50d572cd6,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/37c778346eb67042df5e8edf2485e40a.adoc b/example/docs/asciidoc/GettingStarted/37c778346eb67042df5e8edf2485e40a.adoc new file mode 100644 index 000000000..05093c490 --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/37c778346eb67042df5e8edf2485e40a.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=37c778346eb67042df5e8edf2485e40a,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/3c31f9eb032861bff64abd8b14758991.adoc b/example/docs/asciidoc/GettingStarted/3c31f9eb032861bff64abd8b14758991.adoc new file mode 100644 index 000000000..378fdcae4 --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/3c31f9eb032861bff64abd8b14758991.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=3c31f9eb032861bff64abd8b14758991,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/3d7527bb7ac3b0e1f97b22bdfeb99070.adoc b/example/docs/asciidoc/GettingStarted/3d7527bb7ac3b0e1f97b22bdfeb99070.adoc new file mode 100644 index 000000000..f1363d7ba --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/3d7527bb7ac3b0e1f97b22bdfeb99070.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=3d7527bb7ac3b0e1f97b22bdfeb99070,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/506844befdc5691d835771bcbb1c1a60.adoc b/example/docs/asciidoc/GettingStarted/506844befdc5691d835771bcbb1c1a60.adoc new file mode 100644 index 000000000..80654b237 --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/506844befdc5691d835771bcbb1c1a60.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=506844befdc5691d835771bcbb1c1a60,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/6a8d7b34f2b42d5992aaa1ff147062d9.adoc b/example/docs/asciidoc/GettingStarted/6a8d7b34f2b42d5992aaa1ff147062d9.adoc new file mode 100644 index 000000000..afd3b96cf --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/6a8d7b34f2b42d5992aaa1ff147062d9.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=6a8d7b34f2b42d5992aaa1ff147062d9,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/731621af937d66170347b9cc6b4a3c48.adoc b/example/docs/asciidoc/GettingStarted/731621af937d66170347b9cc6b4a3c48.adoc new file mode 100644 index 000000000..5d80146c3 --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/731621af937d66170347b9cc6b4a3c48.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=731621af937d66170347b9cc6b4a3c48,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/75bda7da7fefff2c16f0423a9aa41c6e.adoc b/example/docs/asciidoc/GettingStarted/75bda7da7fefff2c16f0423a9aa41c6e.adoc new file mode 100644 index 000000000..d25939334 --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/75bda7da7fefff2c16f0423a9aa41c6e.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=75bda7da7fefff2c16f0423a9aa41c6e,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/7d32a32357b5ea8819b72608fcc6fd07.adoc b/example/docs/asciidoc/GettingStarted/7d32a32357b5ea8819b72608fcc6fd07.adoc new file mode 100644 index 000000000..77d1ee180 --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/7d32a32357b5ea8819b72608fcc6fd07.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=7d32a32357b5ea8819b72608fcc6fd07,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/92e3c75133dc4fdb2cc65f149001b40b.adoc b/example/docs/asciidoc/GettingStarted/92e3c75133dc4fdb2cc65f149001b40b.adoc new file mode 100644 index 000000000..7dd904181 --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/92e3c75133dc4fdb2cc65f149001b40b.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=92e3c75133dc4fdb2cc65f149001b40b,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/9c5ef83db886840355ff662b6e9ae8ab.adoc b/example/docs/asciidoc/GettingStarted/9c5ef83db886840355ff662b6e9ae8ab.adoc new file mode 100644 index 000000000..cae96a039 --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/9c5ef83db886840355ff662b6e9ae8ab.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=9c5ef83db886840355ff662b6e9ae8ab,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/b8459547da50aebddbcdd1aaaac02b5f.adoc b/example/docs/asciidoc/GettingStarted/b8459547da50aebddbcdd1aaaac02b5f.adoc new file mode 100644 index 000000000..370279126 --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/b8459547da50aebddbcdd1aaaac02b5f.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=b8459547da50aebddbcdd1aaaac02b5f,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/c181969ef91c3b4a2513c1885be98e26.adoc b/example/docs/asciidoc/GettingStarted/c181969ef91c3b4a2513c1885be98e26.adoc new file mode 100644 index 000000000..899e3d127 --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/c181969ef91c3b4a2513c1885be98e26.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=c181969ef91c3b4a2513c1885be98e26,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/c3fa04519df668d6c27727a12ab09648.adoc b/example/docs/asciidoc/GettingStarted/c3fa04519df668d6c27727a12ab09648.adoc new file mode 100644 index 000000000..9ffab1aab --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/c3fa04519df668d6c27727a12ab09648.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=c3fa04519df668d6c27727a12ab09648,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/db20adb70a8e8d0709d15ba0daf18d23.adoc b/example/docs/asciidoc/GettingStarted/db20adb70a8e8d0709d15ba0daf18d23.adoc new file mode 100644 index 000000000..a08a58724 --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/db20adb70a8e8d0709d15ba0daf18d23.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=db20adb70a8e8d0709d15ba0daf18d23,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/e8035a7476601ad4b136edb250f92d53.adoc b/example/docs/asciidoc/GettingStarted/e8035a7476601ad4b136edb250f92d53.adoc new file mode 100644 index 000000000..68cde70fb --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/e8035a7476601ad4b136edb250f92d53.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=e8035a7476601ad4b136edb250f92d53,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/f8cc4b331a19ff4df8e4a490f906ee69.adoc b/example/docs/asciidoc/GettingStarted/f8cc4b331a19ff4df8e4a490f906ee69.adoc new file mode 100644 index 000000000..b121855b3 --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/f8cc4b331a19ff4df8e4a490f906ee69.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=f8cc4b331a19ff4df8e4a490f906ee69,indent=0] +---- diff --git a/example/docs/asciidoc/GettingStarted/fa5f618ced25ed2e67a1439bb77ba340.adoc b/example/docs/asciidoc/GettingStarted/fa5f618ced25ed2e67a1439bb77ba340.adoc new file mode 100644 index 000000000..44ff61901 --- /dev/null +++ b/example/docs/asciidoc/GettingStarted/fa5f618ced25ed2e67a1439bb77ba340.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../src/Examples/Docs/GettingStarted.py[tag=fa5f618ced25ed2e67a1439bb77ba340,indent=0] +---- diff --git a/example/docs/asciidoc/Setup/Install/CheckRunning/3d1ff6097e2359f927c88c2ccdb36252.adoc b/example/docs/asciidoc/Setup/Install/CheckRunning/3d1ff6097e2359f927c88c2ccdb36252.adoc new file mode 100644 index 000000000..dce28f4ac --- /dev/null +++ b/example/docs/asciidoc/Setup/Install/CheckRunning/3d1ff6097e2359f927c88c2ccdb36252.adoc @@ -0,0 +1,4 @@ +[source,python] +---- +include::./../../../../src/Examples/Docs/Setup/Install/CheckRunning.py[tag=3d1ff6097e2359f927c88c2ccdb36252,indent=0] +---- \ No newline at end of file diff --git a/example/docs/src/Examples/Docs/Docs/Delete.py b/example/docs/src/Examples/Docs/Docs/Delete.py new file mode 100644 index 000000000..1a55efb5e --- /dev/null +++ b/example/docs/src/Examples/Docs/Docs/Delete.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python + +''' +Licensed to Elasticsearch B.V under one or more agreements. +Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +See the LICENSE file in the project root for more information +''' + +from elasticsearch import Elasticsearch + +es = Elasticsearch() + +print("c5e5873783246c7b1c01d8464fed72c4 - L:9") +# tag::c5e5873783246c7b1c01d8464fed72c4[] +response = es.delete( + index='twitter', + id=1, +) +# end::c5e5873783246c7b1c01d8464fed72c4[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("47b5ff897f26e9c943cee5c06034181d - L:84") +# tag::47b5ff897f26e9c943cee5c06034181d[] +response = es.delete( + index='twitter', + id=1, + routing='kimchy', +) +# end::47b5ff897f26e9c943cee5c06034181d[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("d90a84a24a407731dfc1929ac8327746 - L:147") +# tag::d90a84a24a407731dfc1929ac8327746[] +response = es.delete( + index='twitter', + id=1, + timeout='5m', +) +# end::d90a84a24a407731dfc1929ac8327746[] +print("---------------------------------------") +print(response) +print("---------------------------------------") diff --git a/example/docs/src/Examples/Docs/Docs/Get.py b/example/docs/src/Examples/Docs/Docs/Get.py new file mode 100644 index 000000000..3bbb57886 --- /dev/null +++ b/example/docs/src/Examples/Docs/Docs/Get.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python + +''' +Licensed to Elasticsearch B.V under one or more agreements. +Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +See the LICENSE file in the project root for more information +''' + +from elasticsearch import Elasticsearch + +es = Elasticsearch() + +print("fbcf5078a6a9e09790553804054c36b3 - L:9") +# tag::fbcf5078a6a9e09790553804054c36b3[] +response = es.get( + index='twitter', + id=0, +) +# end::fbcf5078a6a9e09790553804054c36b3[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("98234499cfec70487cec5d013e976a84 - L:46") +# tag::98234499cfec70487cec5d013e976a84[] +response = es.exists( + index='twitter', + id=0, +) +# end::98234499cfec70487cec5d013e976a84[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("138ccd89f72aa7502dd9578403dcc589 - L:72") +# tag::138ccd89f72aa7502dd9578403dcc589[] +response = es.get( + index='twitter', + id=0, + _source=False, +) +# end::138ccd89f72aa7502dd9578403dcc589[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("8fdf2344c4fb3de6902ad7c5735270df - L:84") +# tag::8fdf2344c4fb3de6902ad7c5735270df[] +response = es.get( + index='twitter', + id=0, + _source_includes='*.id', + _source_excludes='entities', +) +# end::8fdf2344c4fb3de6902ad7c5735270df[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("745f9b8cdb8e91073f6e520e1d9f8c05 - L:93") +# tag::745f9b8cdb8e91073f6e520e1d9f8c05[] +response = es.get( + index='twitter', + id=0, + _source='*.id,retweeted', +) +# end::745f9b8cdb8e91073f6e520e1d9f8c05[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("913770050ebbf3b9b549a899bc11060a - L:109") +print("TODO") + +print("5eabcdbf61bfcb484dc694f25c2bba36 - L:131") +# tag::5eabcdbf61bfcb484dc694f25c2bba36[] +response = es.index( + index='twitter', + id=1, + body={ + 'counter': 1, + 'tags': ['red'], + }, +) +# end::5eabcdbf61bfcb484dc694f25c2bba36[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("710c7871f20f176d51209b1574b0d61b - L:144") +# tag::710c7871f20f176d51209b1574b0d61b[] +response = es.get( + index='twitter', + id=1, + stored_fields='tags,counter', +) +# end::710c7871f20f176d51209b1574b0d61b[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("0ba0b2db24852abccb7c0fc1098d566e - L:178") +# tag::0ba0b2db24852abccb7c0fc1098d566e[] +response = es.index( + index='twitter', + id=1, + body={ + 'counter': 1, + 'tags': ['white'], + }, + routing='user1', +) +# end::0ba0b2db24852abccb7c0fc1098d566e[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("69a7be47f85138b10437113ab2f0d72d - L:189") +# tag::69a7be47f85138b10437113ab2f0d72d[] +response = es.get( + index='twitter', + id=2, + routing='user1', + stored_fields='tags,counter', +) +# end::69a7be47f85138b10437113ab2f0d72d[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("89a8ac1509936acc272fc2d72907bc45 - L:229") +# tag::89a8ac1509936acc272fc2d72907bc45[] +response = es.get_source( + index='twitter', + id=1, +) +# end::89a8ac1509936acc272fc2d72907bc45[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("d222c6a6ec7a3beca6c97011b0874512 - L:238") +# tag::d222c6a6ec7a3beca6c97011b0874512[] +response = es.get_source( + index='twitter', + id=1, + _source_includes='*.id', + _source_excludes='entities', +) +# end::d222c6a6ec7a3beca6c97011b0874512[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("2468ab381257d759d8a88af1141f6f9c - L:248") +print("TODO") + +print("1d65cb6d055c46a1bde809687d835b71 - L:262") +# tag::1d65cb6d055c46a1bde809687d835b71[] +response = es.get( + index='twitter', + id=1, + routing='user1', +) +# end::1d65cb6d055c46a1bde809687d835b71[] +print("---------------------------------------") +print(response) +print("---------------------------------------") diff --git a/example/docs/src/Examples/Docs/Docs/Index.py b/example/docs/src/Examples/Docs/Docs/Index.py new file mode 100644 index 000000000..843d7aee6 --- /dev/null +++ b/example/docs/src/Examples/Docs/Docs/Index.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python + +''' +Licensed to Elasticsearch B.V under one or more agreements. +Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +See the LICENSE file in the project root for more information +''' + +from elasticsearch import Elasticsearch + +es = Elasticsearch() + +print("bb143628fd04070683eeeadc9406d9cc - L:11") +# tag::bb143628fd04070683eeeadc9406d9cc[] +response = es.index( + index='twitter', + id=1, + body={ + 'user': 'kimchy', + 'post_date': '2009-11-15T14:12:12', + 'message': 'trying out Elasticsearch', + } +) +# end::bb143628fd04070683eeeadc9406d9cc[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("804a97ff4d0613e6568e4efb19c52021 - L:77") +print("TODO") + +print("d718b63cf1b6591a1d59a0cf4fd995eb - L:121") +# tag::d718b63cf1b6591a1d59a0cf4fd995eb[] +response = es.index( + index='twitter', + id=1, + body={ + 'user': 'kimchy', + 'post_date': '2009-11-15T14:12:12', + 'message': 'trying out Elasticsearch', + }, + op_type='create', +) +# end::d718b63cf1b6591a1d59a0cf4fd995eb[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("048d8abd42d094bbdcf4452a58ccb35b - L.:134") +# tag::048d8abd42d094bbdcf4452a58ccb35b[] +response = es.create( + index='twitter', + id=1, + body={ + 'user': 'kimchy', + 'post_date': '2009-11-15T14:12:12', + 'message': 'trying out Elasticsearch', + } +) +# end::048d8abd42d094bbdcf4452a58ccb35b[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("36818c6d9f434d387819c30bd9addb14 - L:153") +# tag::36818c6d9f434d387819c30bd9addb14[] +response = es.index( + index='twitter', + body={ + 'user': 'kimchy', + 'post_date': '2009-11-15T14:12:12', + 'message': 'trying out Elasticsearch', + } +) +# end::36818c6d9f434d387819c30bd9addb14[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("625dc94df1f9affb49a082fd99d41620 - L:204") +# tag::625dc94df1f9affb49a082fd99d41620[] +response = es.index( + index='twitter', + id=1, + body={ + 'user': 'kimchy', + 'post_date': '2009-11-15T14:12:12', + 'message': 'trying out Elasticsearch', + }, + routing='kimchy', +) +# end::625dc94df1f9affb49a082fd99d41620[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("b918d6b798da673a33e49b94f61dcdc0 - L:327") +# tag::b918d6b798da673a33e49b94f61dcdc0[] +response = es.index( + index='twitter', + id=1, + body={ + 'user': 'kimchy', + 'post_date': '2009-11-15T14:12:12', + 'message': 'trying out Elasticsearch', + }, + timeout='5m', +) +# end::b918d6b798da673a33e49b94f61dcdc0[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("1f336ecc62480c1d56351cc2f82d0d08 - L:357") +# tag::1f336ecc62480c1d56351cc2f82d0d08[] +response = es.index( + index='twitter', + id=1, + body={ + 'user': 'kimchy', + 'post_date': '2009-11-15T14:12:12', + 'message': 'trying out Elasticsearch', + }, + version=2, + version_type='external', +) +# end::1f336ecc62480c1d56351cc2f82d0d08[] +print("---------------------------------------") +print(response) +print("---------------------------------------") diff --git a/example/docs/src/Examples/Docs/GettingStarted.py b/example/docs/src/Examples/Docs/GettingStarted.py new file mode 100644 index 000000000..7efb3701b --- /dev/null +++ b/example/docs/src/Examples/Docs/GettingStarted.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python + +''' +Licensed to Elasticsearch B.V under one or more agreements. +Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +See the LICENSE file in the project root for more information +''' + +from elasticsearch import Elasticsearch + +es = Elasticsearch() + +print("f8cc4b331a19ff4df8e4a490f906ee69 - L: 209") +# tag::f8cc4b331a19ff4df8e4a490f906ee69[] +response = es.cat.health(v=True) +# end::f8cc4b331a19ff4df8e4a490f906ee69[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("db20adb70a8e8d0709d15ba0daf18d23 - L:240") +# tag::db20adb70a8e8d0709d15ba0daf18d23[] +response = es.cat.nodes(v=True) +# end::db20adb70a8e8d0709d15ba0daf18d23[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("c3fa04519df668d6c27727a12ab09648 - L:263") +# tag::c3fa04519df668d6c27727a12ab09648[] +response = es.cat.indices(v=True) +# end::c3fa04519df668d6c27727a12ab09648[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("0caf6b6b092ecbcf6f996053678a2384 - L:284") +# tag::0caf6b6b092ecbcf6f996053678a2384[] +# // PUT /customer?pretty +response = es.cat.indices(v=True) +# end::0caf6b6b092ecbcf6f996053678a2384[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("21fe98843fe0b5473f515d21803db409 - L:311") +# tag::21fe98843fe0b5473f515d21803db409[] +response = es.index( + index='customer', + id=1, + body={ + 'name': 'John Doe', + } +) +# end::21fe98843fe0b5473f515d21803db409[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("37a3b66b555aed55618254f50d572cd6 - L:347") +# tag::37a3b66b555aed55618254f50d572cd6[] +response = es.get( + index='customer', + id=1, +) +# end::37a3b66b555aed55618254f50d572cd6[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("92e3c75133dc4fdb2cc65f149001b40b - L:378") +# tag::92e3c75133dc4fdb2cc65f149001b40b[] +es.indices.delete(index='customer') +response = es.cat.indices(v=True) +# end::92e3c75133dc4fdb2cc65f149001b40b[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("fa5f618ced25ed2e67a1439bb77ba340 - L:398") +# tag::fa5f618ced25ed2e67a1439bb77ba340[] +response = es.index( + index='customer', + id=1, + body={ + 'name': 'John Doe', + } +) +response = es.get( + index='customer', + id=1, +) +es.indices.delete(index='customer') +# end::fa5f618ced25ed2e67a1439bb77ba340[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("21fe98843fe0b5473f515d21803db409 - L:431") +# tag::21fe98843fe0b5473f515d21803db409[] +response = es.index( + index='customer', + id=1, + body={ + 'name': 'John Doe', + } +) +# end::21fe98843fe0b5473f515d21803db409[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("75bda7da7fefff2c16f0423a9aa41c6e - L:442") +# tag::75bda7da7fefff2c16f0423a9aa41c6e[] +response = es.index( + index='customer', + id=2, + body={ + 'name': 'Jane Doe', + } +) +# end::75bda7da7fefff2c16f0423a9aa41c6e[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("37c778346eb67042df5e8edf2485e40a - L:454") +# tag::37c778346eb67042df5e8edf2485e40a[] +response = es.index( + index='customer', + body={ + 'name': 'Jane Doe', + } +) +# end::37c778346eb67042df5e8edf2485e40a[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("1c0f3b0bc4b7e53b53755fd3bda5b8cf - L:470") +# tag::1c0f3b0bc4b7e53b53755fd3bda5b8cf[] +response = es.index( + index='customer', + body={ + 'name': 'Jane Doe', + } +) +# end::1c0f3b0bc4b7e53b53755fd3bda5b8cf[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + + +print("6a8d7b34f2b42d5992aaa1ff147062d9 - L:489") +# tag::6a8d7b34f2b42d5992aaa1ff147062d9[] +response = es.update( + index='customer', + id=1, + body={ + 'doc': { + 'name': 'Jane Doe', + }, + }, +) +# end::6a8d7b34f2b42d5992aaa1ff147062d9[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("731621af937d66170347b9cc6b4a3c48 - L:501") +# tag::731621af937d66170347b9cc6b4a3c48[] +response = es.update( + index='customer', + id=1, + body={ + 'doc': { + 'name': 'Jane Doe', + 'age': 20, + }, + }, +) +# end::731621af937d66170347b9cc6b4a3c48[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("38dfa309717488362d0f784e17ebd1b5 - L:513") +print("TODO") + +print("9c5ef83db886840355ff662b6e9ae8ab - L:532") +# tag::9c5ef83db886840355ff662b6e9ae8ab[] +response = es.delete( + index='customer', + id=2, +) +# end::9c5ef83db886840355ff662b6e9ae8ab[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("7d32a32357b5ea8819b72608fcc6fd07 - L:550") +# tag::7d32a32357b5ea8819b72608fcc6fd07[] +response = es.bulk(body=[ + {'index': {'_index': 'customer', '_id': 1}}, + {'name': 'John Doe'}, + {'index': {'_index': 'customer', '_id': 2}}, + {'name': 'Jane Doe'}, +]) +# end::7d32a32357b5ea8819b72608fcc6fd07[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("193864342d9f0a36ec84a91ca325f5ec - L:562") +# tag::193864342d9f0a36ec84a91ca325f5ec[] +response = es.bulk(body=[ + {'index': {'_index': 'customer', '_id': 1}}, + {'name': 'John Doe'}, + {'delete': {'_index': 'customer', '_id': 2}}, +]) +# end::193864342d9f0a36ec84a91ca325f5ec[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("c181969ef91c3b4a2513c1885be98e26 - L:647") +# tag::c181969ef91c3b4a2513c1885be98e26[] +response = es.search( + index='bank', + q='*', + sort={ + 'account_number': 'asc', + }, +) +# end::c181969ef91c3b4a2513c1885be98e26[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("506844befdc5691d835771bcbb1c1a60 - L:720") +# tag::506844befdc5691d835771bcbb1c1a60[] +response = es.search( + index='bank', + body={ + 'query': { + 'match_all': {}, + }, + }, + sort={ + 'account_number': 'asc', + }, +) +# end::506844befdc5691d835771bcbb1c1a60[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("345ea7e9cb5af9e052ce0cf6f1f52c23 - L:789") +# tag::345ea7e9cb5af9e052ce0cf6f1f52c23[] +response = es.search( + index='bank', + body={ + 'query': { + 'match_all': {}, + }, + }, +) +# end::345ea7e9cb5af9e052ce0cf6f1f52c23[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("3d7527bb7ac3b0e1f97b22bdfeb99070 - L:805") +# tag::3d7527bb7ac3b0e1f97b22bdfeb99070[] +response = es.search( + index='bank', + body={ + 'query': { + 'match_all': {}, + }, + }, + size=1, +) +# end::3d7527bb7ac3b0e1f97b22bdfeb99070[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("3c31f9eb032861bff64abd8b14758991 - L:820") +# tag::3c31f9eb032861bff64abd8b14758991[] +response = es.search( + index='bank', + body={ + 'query': { + 'match_all': {}, + }, + }, + from_=10, + size=10, +) +# end::3c31f9eb032861bff64abd8b14758991[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("e8035a7476601ad4b136edb250f92d53 - L:836") +# tag::e8035a7476601ad4b136edb250f92d53[] +response = es.search( + index='bank', + body={ + 'query': { + 'match_all': {}, + }, + }, + sort={ + 'balance': 'desc', + }, +) +# end::e8035a7476601ad4b136edb250f92d53[] +print("---------------------------------------") +print(response) +print("---------------------------------------") + +print("b8459547da50aebddbcdd1aaaac02b5f - L:854") +# tag::b8459547da50aebddbcdd1aaaac02b5f[] +response = es.search( + index='bank', + body={ + 'query': { + 'match_all': {}, + }, + }, + _source=['account_number', 'balance'], +) +# end::b8459547da50aebddbcdd1aaaac02b5f[] +print("---------------------------------------") +print(response) +print("---------------------------------------") diff --git a/example/docs/src/Examples/Docs/Setup/Install/CheckRunning.py b/example/docs/src/Examples/Docs/Setup/Install/CheckRunning.py new file mode 100644 index 000000000..3978d53ce --- /dev/null +++ b/example/docs/src/Examples/Docs/Setup/Install/CheckRunning.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python + +''' +Licensed to Elasticsearch B.V under one or more agreements. +Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +See the LICENSE file in the project root for more information +''' + +from elasticsearch import Elasticsearch + +es = Elasticsearch() + +print("3d1ff6097e2359f927c88c2ccdb36252 - L:7") +# tag::3d1ff6097e2359f927c88c2ccdb36252[] +response = es.info() +# end::3d1ff6097e2359f927c88c2ccdb36252[] +print("---------------------------------------") +print(response) +print("---------------------------------------") diff --git a/example/load.py b/example/load.py index b500f9449..a69234297 100644 --- a/example/load.py +++ b/example/load.py @@ -14,65 +14,56 @@ from elasticsearch.exceptions import TransportError from elasticsearch.helpers import bulk, streaming_bulk + def create_git_index(client, index): # we will use user on several places user_mapping = { - 'properties': { - 'name': { - 'type': 'text', - 'fields': { - 'keyword': {'type': 'keyword'}, - } + "properties": { + "name": {"type": "text", "fields": {"keyword": {"type": "keyword"}}} } - } } create_index_body = { - 'settings': { - # just one shard, no replicas for testing - 'number_of_shards': 1, - 'number_of_replicas': 0, - - # custom analyzer for analyzing file paths - 'analysis': { - 'analyzer': { - 'file_path': { - 'type': 'custom', - 'tokenizer': 'path_hierarchy', - 'filter': ['lowercase'] + "settings": { + # just one shard, no replicas for testing + "number_of_shards": 1, + "number_of_replicas": 0, + # custom analyzer for analyzing file paths + "analysis": { + "analyzer": { + "file_path": { + "type": "custom", + "tokenizer": "path_hierarchy", + "filter": ["lowercase"], + } + } + }, + }, + "mappings": { + "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 try: - client.indices.create( - index=index, - body=create_index_body, - ) + client.indices.create(index=index, body=create_index_body) except TransportError as e: # ignore already existing index - if e.error == 'index_already_exists_exception': + if e.error == "resource_already_exists_exception": pass else: raise + def parse_commits(head, name): """ Go through the git repository log and generate a document per commit @@ -80,26 +71,24 @@ def parse_commits(head, name): """ for commit in head.traverse(): yield { - '_id': commit.hexsha, - 'repository': name, - 'committed_date': datetime.fromtimestamp(commit.committed_date), - 'committer': { - 'name': commit.committer.name, - 'email': commit.committer.email, - }, - 'authored_date': datetime.fromtimestamp(commit.authored_date), - 'author': { - 'name': commit.author.name, - 'email': commit.author.email, + "_id": commit.hexsha, + "repository": name, + "committed_date": datetime.fromtimestamp(commit.committed_date), + "committer": { + "name": commit.committer.name, + "email": commit.committer.email, }, - 'description': commit.message, - 'parent_shas': [p.hexsha for p in commit.parents], + "authored_date": datetime.fromtimestamp(commit.authored_date), + "author": {"name": commit.author.name, "email": commit.author.email}, + "description": commit.message, + "parent_shas": [p.hexsha for p in commit.parents], # we only care about the filenames, not the per-file stats - 'files': list(commit.stats.files), - 'stats': commit.stats.total, + "files": list(commit.stats.files), + "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 using `client`. If the index doesn't exist it will be created. @@ -114,18 +103,17 @@ def load_repo(client, path=None, index='git'): # in - since the `parse_commits` function is a generator this will avoid # loading all the commits into memory for ok, result in streaming_bulk( - client, - parse_commits(repo.refs.master.commit, repo_name), - index=index, - doc_type='doc', - chunk_size=50 # keep the batch sizes small for appearances only - ): + client, + parse_commits(repo.refs.master.commit, repo_name), + index=index, + chunk_size=50, # keep the batch sizes small for appearances only + ): 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 # successfully indexed 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: print(doc_id) @@ -133,36 +121,40 @@ def load_repo(client, path=None, index='git'): # we manually update some documents to add additional information UPDATES = [ { - '_type': 'doc', - '_id': '20fbba1230cabbc0f4644f917c6c2be52b8a63e8', - '_op_type': 'update', - 'doc': {'initial_commit': True} + "_type": "_doc", + "_id": "20fbba1230cabbc0f4644f917c6c2be52b8a63e8", + "_op_type": "update", + "doc": {"initial_commit": True}, }, { - '_type': 'doc', - '_id': 'ae0073c8ca7e24d237ffd56fba495ed409081bf4', - '_op_type': 'update', - 'doc': {'release': '5.0.0'} + "_type": "_doc", + "_id": "ae0073c8ca7e24d237ffd56fba495ed409081bf4", + "_op_type": "update", + "doc": {"release": "5.0.0"}, }, ] -if __name__ == '__main__': +if __name__ == "__main__": # get trace logger and set level - tracer = logging.getLogger('elasticsearch.trace') + tracer = logging.getLogger("elasticsearch.trace") tracer.setLevel(logging.INFO) - tracer.addHandler(logging.FileHandler('/tmp/es_trace.log')) + tracer.addHandler(logging.FileHandler("/tmp/es_trace.log")) parser = argparse.ArgumentParser() parser.add_argument( - "-H", "--host", + "-H", + "--host", action="store", 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( - "-p", "--path", + "-p", + "--path", action="store", 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() @@ -173,15 +165,17 @@ def load_repo(client, path=None, index='git'): load_repo(es, path=args.path) # run the bulk operations - success, _ = bulk(es, UPDATES, index='git') - print('Performed %d actions' % success) + success, _ = bulk(es, UPDATES, index="git") + print("Performed %d actions" % success) # we can now make docs visible for searching - es.indices.refresh(index='git') + es.indices.refresh(index="git") # now we can retrieve the documents - initial_commit = es.get(index='git', doc_type='doc', id='20fbba1230cabbc0f4644f917c6c2be52b8a63e8') - print('%s: %s' % (initial_commit['_id'], initial_commit['_source']['committed_date'])) + initial_commit = es.get(index="git", id="20fbba1230cabbc0f4644f917c6c2be52b8a63e8") + print( + "%s: %s" % (initial_commit["_id"], initial_commit["_source"]["committed_date"]) + ) # 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") diff --git a/example/queries.py b/example/queries.py index 4d61c2623..d7cc0772f 100644 --- a/example/queries.py +++ b/example/queries.py @@ -6,95 +6,91 @@ from elasticsearch import Elasticsearch + def print_search_stats(results): - print('=' * 80) - print('Total %d found in %dms' % (results['hits']['total'], results['took'])) - print('-' * 80) + print("=" * 80) + print( + "Total %d found in %dms" % (results["hits"]["total"]["value"], results["took"]) + ) + print("-" * 80) + def print_hits(results): " Simple utility function to print results of a search query. " 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 - created_at = parse_date(hit['_source'].get('created_at', hit['_source']['authored_date'])) - print('/%s/%s/%s (%s): %s' % ( - hit['_index'], hit['_type'], hit['_id'], - created_at.strftime('%Y-%m-%d'), - hit['_source']['description'].split('\n')[0])) + created_at = parse_date( + hit["_source"].get("created_at", hit["_source"]["authored_date"]) + ) + print( + "/%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() + # get trace logger and set level -tracer = logging.getLogger('elasticsearch.trace') +tracer = logging.getLogger("elasticsearch.trace") 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 es = Elasticsearch() -print('Empty search:') -print_hits(es.search(index='git')) +print("Empty search:") +print_hits(es.search(index="git")) print('Find commits that says "fix" without touching tests:') result = es.search( - index='git', - doc_type='doc', + index="git", body={ - 'query': { - 'bool': { - 'must': { - 'match': {'description': 'fix'} - }, - 'must_not': { - 'term': {'files': 'test_elasticsearch'} - } + "query": { + "bool": { + "must": {"match": {"description": "fix"}}, + "must_not": {"term": {"files": "test_elasticsearch"}}, + } } - } - } + }, ) print_hits(result) -print('Last 8 Commits for elasticsearch-py:') +print("Last 8 Commits for elasticsearch-py:") result = es.search( - index='git', - doc_type='doc', + index="git", body={ - 'query': { - 'term': { - 'repository': 'elasticsearch-py' - } - }, - 'sort': [ - {'committed_date': {'order': 'desc'}} - ], - 'size': 8 - } + "query": {"term": {"repository": "elasticsearch-py"}}, + "sort": [{"committed_date": {"order": "desc"}}], + "size": 8, + }, ) print_hits(result) -print('Stats for top 10 committers:') +print("Stats for top 10 committers:") result = es.search( - index='git', - doc_type='doc', + index="git", body={ - 'size': 0, - 'aggs': { - 'committers': { - 'terms': { - 'field': 'committer.name.keyword', - }, - 'aggs': { - 'line_stats': { - 'stats': {'field': 'stats.lines'} + "size": 0, + "aggs": { + "committers": { + "terms": {"field": "committer.name.keyword"}, + "aggs": {"line_stats": {"stats": {"field": "stats.lines"}}}, } - } - } - } - } + }, + }, ) print_search_stats(result) -for committer in result['aggregations']['committers']['buckets']: - print('%15s: %3d commits changing %6d lines' % ( - committer['key'], committer['doc_count'], committer['line_stats']['sum'])) -print('=' * 80) +for committer in result["aggregations"]["committers"]["buckets"]: + print( + "%15s: %3d commits changing %6d lines" + % (committer["key"], committer["doc_count"], committer["line_stats"]["sum"]) + ) +print("=" * 80) diff --git a/setup.py b/setup.py index 64b9f49f4..5436d941e 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages import sys -VERSION = (7, 0, 0) +VERSION = (7, 1, 0) __version__ = VERSION __versionstr__ = ".".join(map(str, VERSION)) @@ -42,14 +42,13 @@ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.2", - "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ], diff --git a/test_elasticsearch/run_tests.py b/test_elasticsearch/run_tests.py index 44928f9d8..9a2c530e0 100755 --- a/test_elasticsearch/run_tests.py +++ b/test_elasticsearch/run_tests.py @@ -54,7 +54,7 @@ def fetch_es_repo(): shell=True, ) # reset to the version fron info() - subprocess.check_call("cd %s && git pull" % repo_path, shell=True) + subprocess.check_call("cd %s && git fetch" % repo_path, shell=True) subprocess.check_call("cd %s && git reset --hard %s" % (repo_path, sha), shell=True) diff --git a/test_elasticsearch/test_cases.py b/test_elasticsearch/test_cases.py index 43550e5cc..6dec2b3c1 100644 --- a/test_elasticsearch/test_cases.py +++ b/test_elasticsearch/test_cases.py @@ -1,4 +1,5 @@ from collections import defaultdict + try: # python 2.6 from unittest2 import TestCase, SkipTest @@ -7,6 +8,7 @@ from elasticsearch import Elasticsearch + class DummyTransport(object): def __init__(self, hosts, responses=None, **kwargs): self.hosts = hosts @@ -46,7 +48,7 @@ def test_start_with_0_call(self): self.assert_call_count_equals(0) def test_each_call_is_recorded(self): - self.client.transport.perform_request('GET', '/') - self.client.transport.perform_request('DELETE', '/42', params={}, body='body') + self.client.transport.perform_request("GET", "/") + self.client.transport.perform_request("DELETE", "/42", params={}, body="body") 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)) diff --git a/test_elasticsearch/test_client/__init__.py b/test_elasticsearch/test_client/__init__.py index 8e0c67ca3..4cd410a6b 100644 --- a/test_elasticsearch/test_client/__init__.py +++ b/test_elasticsearch/test_client/__init__.py @@ -96,4 +96,4 @@ def test_index_uses_post_if_id_is_empty(self): def test_index_uses_put_if_id_is_not_empty(self): self.client.index(index="my-index", id=0, body={}) - self.assert_url_called("PUT", "/my-index/_doc/0") + self.assert_url_called("POST", "/my-index/_doc/0") diff --git a/test_elasticsearch/test_client/test_indices.py b/test_elasticsearch/test_client/test_indices.py index fbb8c23c6..7d80562a7 100644 --- a/test_elasticsearch/test_client/test_indices.py +++ b/test_elasticsearch/test_client/test_indices.py @@ -1,19 +1,20 @@ from test_elasticsearch.test_cases import ElasticsearchTestCase + class TestIndices(ElasticsearchTestCase): def test_create_one_index(self): - self.client.indices.create('test-index') - self.assert_url_called('PUT', '/test-index') + self.client.indices.create("test-index") + self.assert_url_called("PUT", "/test-index") def test_delete_multiple_indices(self): - self.client.indices.delete(['test-index', 'second.index', 'third/index']) - self.assert_url_called('DELETE', '/test-index,second.index,third%2Findex') + self.client.indices.delete(["test-index", "second.index", "third/index"]) + self.assert_url_called("DELETE", "/test-index,second.index,third%2Findex") def test_exists_index(self): - self.client.indices.exists('second.index,third/index') - self.assert_url_called('HEAD', '/second.index,third%2Findex') + self.client.indices.exists("second.index,third/index") + self.assert_url_called("HEAD", "/second.index,third%2Findex") 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=[]) - self.assertRaises(ValueError, self.client.indices.exists, index='') + self.assertRaises(ValueError, self.client.indices.exists, index="") diff --git a/test_elasticsearch/test_client/test_utils.py b/test_elasticsearch/test_client/test_utils.py index 66a832930..e30d8b14c 100644 --- a/test_elasticsearch/test_client/test_utils.py +++ b/test_elasticsearch/test_client/test_utils.py @@ -6,35 +6,32 @@ from ..test_cases import TestCase, SkipTest + class TestMakePath(TestCase): def test_handles_unicode(self): 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): if not PY2: - raise SkipTest('Only relevant for py2') - id = "中文".encode('utf-8') - self.assertEquals('/some-index/type/%E4%B8%AD%E6%96%87', _make_path('some-index', 'type', id)) + raise SkipTest("Only relevant for py2") + id = "中文".encode("utf-8") + self.assertEquals( + "/some-index/type/%E4%B8%AD%E6%96%87", _make_path("some-index", "type", id) + ) class TestEscape(TestCase): def test_handles_ascii(self): string = "abc123" - self.assertEquals( - b'abc123', - _escape(string) - ) + self.assertEquals(b"abc123", _escape(string)) + def test_handles_unicode(self): string = "中文" - self.assertEquals( - b'\xe4\xb8\xad\xe6\x96\x87', - _escape(string) - ) + self.assertEquals(b"\xe4\xb8\xad\xe6\x96\x87", _escape(string)) def test_handles_bytestring(self): - string = b'celery-task-meta-c4f1201f-eb7b-41d5-9318-a75a8cfbdaa0' - self.assertEquals( - string, - _escape(string) - ) + string = b"celery-task-meta-c4f1201f-eb7b-41d5-9318-a75a8cfbdaa0" + self.assertEquals(string, _escape(string)) diff --git a/test_elasticsearch/test_connection.py b/test_elasticsearch/test_connection.py index d35387542..073ce6bd1 100644 --- a/test_elasticsearch/test_connection.py +++ b/test_elasticsearch/test_connection.py @@ -5,14 +5,19 @@ import urllib3 import warnings from requests.auth import AuthBase - -from elasticsearch.exceptions import TransportError, ConflictError, RequestError, NotFoundError -from elasticsearch.connection import RequestsHttpConnection, \ - Urllib3HttpConnection +from platform import python_version + +from elasticsearch.exceptions import ( + TransportError, + ConflictError, + RequestError, + NotFoundError, +) +from elasticsearch.connection import RequestsHttpConnection, Urllib3HttpConnection from elasticsearch.exceptions import ImproperlyConfigured +from elasticsearch import __versionstr__ from .test_cases import TestCase, SkipTest - class TestUrllib3Connection(TestCase): def test_ssl_context(self): try: @@ -22,20 +27,46 @@ def test_ssl_context(self): # it means SSLContext is not available for that version of python # and we should skip this test. 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) self.assertEqual(len(con.pool.conn_kw.keys()), 1) - self.assertIsInstance( - con.pool.conn_kw['ssl_context'], - ssl.SSLContext + self.assertIsInstance(con.pool.conn_kw["ssl_context"], ssl.SSLContext) + self.assertTrue(con.use_ssl) + + def test_http_cloud_id(self): + con = Urllib3HttpConnection( + cloud_id="foobar:ZXhhbXBsZS5jbG91ZC5jb20kMGZkNTBmNjIzMjBlZDY1MzlmNmNiNDhlMWI2OCRhYzUzOTVhODgz\nNDU2NmM5ZjE1Y2Q4ZTQ5MGE=\n" ) self.assertTrue(con.use_ssl) + self.assertEquals( + con.host, "https://0fd50f62320ed6539f6cb48e1b68.example.cloud.com:9243" + ) + + def test_api_key_auth(self): + # test with tuple + con = Urllib3HttpConnection( + cloud_id="foobar:ZXhhbXBsZS5jbG91ZC5jb20kMGZkNTBmNjIzMjBlZDY1MzlmNmNiNDhlMWI2OCRhYzUzOTVhODgz\nNDU2NmM5ZjE1Y2Q4ZTQ5MGE=\n", + api_key=("elastic", "changeme1"), + ) + self.assertEquals(con.headers["authorization"], "ApiKey ZWxhc3RpYzpjaGFuZ2VtZTE=") + + # test with base64 encoded string + con = Urllib3HttpConnection( + cloud_id="foobar:ZXhhbXBsZS5jbG91ZC5jb20kMGZkNTBmNjIzMjBlZDY1MzlmNmNiNDhlMWI2OCRhYzUzOTVhODgz\nNDU2NmM5ZjE1Y2Q4ZTQ5MGE=\n", + api_key="ZWxhc3RpYzpjaGFuZ2VtZTI=", + ) + self.assertEquals(con.headers["authorization"], "ApiKey ZWxhc3RpYzpjaGFuZ2VtZTI=") def test_http_compression(self): con = Urllib3HttpConnection(http_compress=True) self.assertTrue(con.http_compress) - self.assertEquals(con.headers['content-encoding'], 'gzip') + self.assertEquals(con.headers["content-encoding"], "gzip") + + def test_default_user_agent(self): + con = Urllib3HttpConnection() + self.assertEquals(con._get_default_user_agent(), "elasticsearch-py/%s (Python %s)" % (__versionstr__, python_version())) def test_timeout_set(self): con = Urllib3HttpConnection(timeout=42) @@ -43,40 +74,67 @@ def test_timeout_set(self): def test_keep_alive_is_on_by_default(self): con = Urllib3HttpConnection() - self.assertEquals({'connection': 'keep-alive', - 'content-type': 'application/json'}, con.headers) + self.assertEquals( + { + "connection": "keep-alive", + "content-type": "application/json", + "user-agent": con._get_default_user_agent(), + }, + con.headers, + ) def test_http_auth(self): - con = Urllib3HttpConnection(http_auth='username:secret') - self.assertEquals({ - 'authorization': 'Basic dXNlcm5hbWU6c2VjcmV0', - 'connection': 'keep-alive', - 'content-type': 'application/json' - }, con.headers) + con = Urllib3HttpConnection(http_auth="username:secret") + self.assertEquals( + { + "authorization": "Basic dXNlcm5hbWU6c2VjcmV0", + "connection": "keep-alive", + "content-type": "application/json", + "user-agent": con._get_default_user_agent(), + }, + con.headers, + ) def test_http_auth_tuple(self): - con = Urllib3HttpConnection(http_auth=('username', 'secret')) - self.assertEquals({'authorization': 'Basic dXNlcm5hbWU6c2VjcmV0', - 'content-type': 'application/json', - 'connection': 'keep-alive'}, con.headers) + con = Urllib3HttpConnection(http_auth=("username", "secret")) + self.assertEquals( + { + "authorization": "Basic dXNlcm5hbWU6c2VjcmV0", + "content-type": "application/json", + "connection": "keep-alive", + "user-agent": con._get_default_user_agent(), + }, + con.headers, + ) def test_http_auth_list(self): - con = Urllib3HttpConnection(http_auth=['username', 'secret']) - self.assertEquals({'authorization': 'Basic dXNlcm5hbWU6c2VjcmV0', - 'content-type': 'application/json', - 'connection': 'keep-alive'}, con.headers) + con = Urllib3HttpConnection(http_auth=["username", "secret"]) + self.assertEquals( + { + "authorization": "Basic dXNlcm5hbWU6c2VjcmV0", + "content-type": "application/json", + "connection": "keep-alive", + "user-agent": con._get_default_user_agent(), + }, + con.headers, + ) def test_uses_https_if_verify_certs_is_off(self): with warnings.catch_warnings(record=True) as w: con = Urllib3HttpConnection(use_ssl=True, verify_certs=False) 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) def nowarn_when_test_uses_https_if_verify_certs_is_off(self): 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.assertIsInstance(con.pool, urllib3.HTTPSConnectionPool) @@ -85,9 +143,13 @@ def test_doesnt_use_https_if_not_specified(self): con = Urllib3HttpConnection() self.assertIsInstance(con.pool, urllib3.HTTPConnectionPool) + 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) + def _dummy_send(*args, **kwargs): dummy_response = Mock() dummy_response.headers = {} @@ -97,20 +159,21 @@ def _dummy_send(*args, **kwargs): dummy_response.cookies = {} _dummy_send.call_args = (args, kwargs) return dummy_response + con.session.send = _dummy_send return con def _get_request(self, connection, *args, **kwargs): - if 'body' in kwargs: - kwargs['body'] = kwargs['body'].encode('utf-8') + if "body" in kwargs: + kwargs["body"] = kwargs["body"].encode("utf-8") status, headers, data = connection.perform_request(*args, **kwargs) 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 - self.assertEquals(timeout, kwargs['timeout']) + self.assertEquals(timeout, kwargs["timeout"]) self.assertEquals(1, len(args)) return args[0] @@ -124,75 +187,137 @@ def test_timeout_set(self): con = RequestsHttpConnection(timeout=42) self.assertEquals(42, con.timeout) + def test_http_cloud_id(self): + con = RequestsHttpConnection( + cloud_id="foobar:ZXhhbXBsZS5jbG91ZC5jb20kMGZkNTBmNjIzMjBlZDY1MzlmNmNiNDhlMWI2OCRhYzUzOTVhODgz\nNDU2NmM5ZjE1Y2Q4ZTQ5MGE=\n" + ) + self.assertTrue(con.use_ssl) + self.assertEquals( + con.host, "https://0fd50f62320ed6539f6cb48e1b68.example.cloud.com:9243" + ) + + def test_api_key_auth(self): + # test with tuple + con = RequestsHttpConnection( + cloud_id="foobar:ZXhhbXBsZS5jbG91ZC5jb20kMGZkNTBmNjIzMjBlZDY1MzlmNmNiNDhlMWI2OCRhYzUzOTVhODgz\nNDU2NmM5ZjE1Y2Q4ZTQ5MGE=\n", + api_key=("elastic", "changeme1"), + ) + self.assertEquals(con.session.headers["authorization"], "ApiKey ZWxhc3RpYzpjaGFuZ2VtZTE=") + + # test with base64 encoded string + con = RequestsHttpConnection( + cloud_id="foobar:ZXhhbXBsZS5jbG91ZC5jb20kMGZkNTBmNjIzMjBlZDY1MzlmNmNiNDhlMWI2OCRhYzUzOTVhODgz\nNDU2NmM5ZjE1Y2Q4ZTQ5MGE=\n", + api_key="ZWxhc3RpYzpjaGFuZ2VtZTI=", + ) + self.assertEquals(con.session.headers["authorization"], "ApiKey ZWxhc3RpYzpjaGFuZ2VtZTI=") + def test_uses_https_if_verify_certs_is_off(self): 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('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('GET', request.method) + self.assertEquals("https://localhost:9200/url/", request.url) + self.assertEquals("GET", request.method) self.assertEquals(None, request.body) def nowarn_when_test_uses_https_if_verify_certs_is_off(self): 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)) - request = self._get_request(con, 'GET', '/') + request = self._get_request(con, "GET", "/") - self.assertEquals('https://localhost:9200/url/', request.url) - self.assertEquals('GET', request.method) + self.assertEquals("https://localhost:9200/url/", request.url) + self.assertEquals("GET", request.method) self.assertEquals(None, request.body) def test_merge_headers(self): - con = self._get_mock_connection(connection_params={'headers': {'h1': 'v1', 'h2': 'v2'}}) - req = self._get_request(con, 'GET', '/', headers={'h2': 'v2p', 'h3': 'v3'}) - self.assertEquals(req.headers['h1'], 'v1') - self.assertEquals(req.headers['h2'], 'v2p') - self.assertEquals(req.headers['h3'], 'v3') + con = self._get_mock_connection( + connection_params={"headers": {"h1": "v1", "h2": "v2"}} + ) + req = self._get_request(con, "GET", "/", headers={"h2": "v2p", "h3": "v3"}) + self.assertEquals(req.headers["h1"], "v1") + self.assertEquals(req.headers["h2"], "v2p") + self.assertEquals(req.headers["h3"], "v3") + + def test_default_headers(self): + con = self._get_mock_connection() + req = self._get_request(con, "GET", "/") + self.assertEquals(req.headers["content-type"], "application/json") + self.assertEquals(req.headers["user-agent"], con._get_default_user_agent()) + + def test_custom_headers(self): + con = self._get_mock_connection() + req = self._get_request(con, "GET", "/", headers={ + "content-type": "application/x-ndjson", + "user-agent": "custom-agent/1.2.3", + }) + self.assertEquals(req.headers["content-type"], "application/x-ndjson") + self.assertEquals(req.headers["user-agent"], "custom-agent/1.2.3") def test_http_auth(self): - con = RequestsHttpConnection(http_auth='username:secret') - self.assertEquals(('username', 'secret'), con.session.auth) + con = RequestsHttpConnection(http_auth="username:secret") + self.assertEquals(("username", "secret"), con.session.auth) def test_http_auth_tuple(self): - con = RequestsHttpConnection(http_auth=('username', 'secret')) - self.assertEquals(('username', 'secret'), con.session.auth) + con = RequestsHttpConnection(http_auth=("username", "secret")) + self.assertEquals(("username", "secret"), con.session.auth) def test_http_auth_list(self): - con = RequestsHttpConnection(http_auth=['username', 'secret']) - self.assertEquals(('username', 'secret'), con.session.auth) + con = RequestsHttpConnection(http_auth=["username", "secret"]) + self.assertEquals(("username", "secret"), con.session.auth) def test_repr(self): con = self._get_mock_connection({"host": "elasticsearch.com", "port": 443}) - self.assertEquals('', repr(con)) + self.assertEquals( + "", repr(con) + ) def test_conflict_error_is_returned_on_409(self): 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): 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): 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): 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) - @patch('elasticsearch.connection.base.tracer') - @patch('elasticsearch.connection.base.logger') + @patch("elasticsearch.connection.base.tracer") + @patch("elasticsearch.connection.base.logger") def test_failed_request_logs_and_traces(self, logger, tracer): 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 self.assertEquals(1, tracer.info.call_count) @@ -200,90 +325,101 @@ def test_failed_request_logs_and_traces(self, logger, tracer): self.assertEquals(1, tracer.debug.call_count) # log url and duration self.assertEquals(1, logger.warning.call_count) - self.assertTrue(re.match( - '^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:] - )) + self.assertTrue( + re.match( + "^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.logger') + @patch("elasticsearch.connection.base.tracer") + @patch("elasticsearch.connection.base.logger") def test_success_logs_and_traces(self, logger, tracer): - 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')) + 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"), + ) # trace request self.assertEquals(1, tracer.info.call_count) self.assertEquals( """curl -H 'Content-Type: application/json' -XGET 'http://localhost:9200/?pretty¶m=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 self.assertEquals(1, tracer.debug.call_count) - self.assertTrue(re.match( - '#\[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:] - )) + self.assertTrue( + re.match( + '#\[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 self.assertEquals(1, logger.info.call_count) - self.assertTrue(re.match( - '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:] - )) + self.assertTrue( + re.match( + "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 self.assertEquals(2, logger.debug.call_count) req, resp = logger.debug.call_args_list - self.assertEquals( - '> {"question": "what\'s that?"}', - req[0][0] % req[0][1:] - ) - self.assertEquals( - '< {"answer": "that\'s it!"}', - resp[0][0] % resp[0][1:] - ) + self.assertEquals('> {"question": "what\'s that?"}', req[0][0] % req[0][1:]) + self.assertEquals('< {"answer": "that\'s it!"}', resp[0][0] % resp[0][1:]) def test_defaults(self): 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('GET', request.method) + self.assertEquals("http://localhost:9200/", request.url) + self.assertEquals("GET", request.method) self.assertEquals(None, request.body) def test_params_properly_encoded(self): 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('GET', request.method) + self.assertEquals("http://localhost:9200/?param=value+with+spaces", request.url) + self.assertEquals("GET", request.method) self.assertEquals(None, request.body) def test_body_attached(self): 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('GET', request.method) - self.assertEquals('{"answer": 42}'.encode('utf-8'), request.body) + self.assertEquals("http://localhost:9200/", request.url) + self.assertEquals("GET", request.method) + self.assertEquals('{"answer": 42}'.encode("utf-8"), request.body) def test_http_auth_attached(self): - con = self._get_mock_connection({'http_auth': 'username:secret'}) - request = self._get_request(con, 'GET', '/') + con = self._get_mock_connection({"http_auth": "username:secret"}) + 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): 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('GET', request.method) - self.assertEquals('{"answer": 42}'.encode('utf-8'), request.body) + self.assertEquals("http://localhost:9200/some-prefix/_search", request.url) + self.assertEquals("GET", request.method) + self.assertEquals('{"answer": 42}'.encode("utf-8"), request.body) # trace request self.assertEquals(1, tracer.info.call_count) self.assertEquals( "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:], ) diff --git a/test_elasticsearch/test_connection_pool.py b/test_elasticsearch/test_connection_pool.py index fdbe16fdd..f5a5f2d85 100644 --- a/test_elasticsearch/test_connection_pool.py +++ b/test_elasticsearch/test_connection_pool.py @@ -1,14 +1,22 @@ import time -from elasticsearch.connection_pool import ConnectionPool, RoundRobinSelector, DummyConnectionPool +from elasticsearch.connection_pool import ( + ConnectionPool, + RoundRobinSelector, + DummyConnectionPool, +) +from elasticsearch.connection import Connection from elasticsearch.exceptions import ImproperlyConfigured from .test_cases import TestCase + class TestConnectionPool(TestCase): def test_dummy_cp_raises_exception_on_more_connections(self): 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): self.assertRaises(ImproperlyConfigured, ConnectionPool, []) @@ -32,13 +40,20 @@ def test_disable_shuffling(self): def test_selectors_have_access_to_connection_opts(self): class MySelector(RoundRobinSelector): def select(self, connections): - return self.connection_opts[super(MySelector, self).select(connections)]["actual"] - pool = ConnectionPool([(x, {"actual": x*x}) for x in range(100)], selector_class=MySelector, randomize_hosts=False) + return self.connection_opts[ + super(MySelector, self).select(connections) + ]["actual"] + + pool = ConnectionPool( + [(x, {"actual": x * x}) for x in range(100)], + selector_class=MySelector, + randomize_hosts=False, + ) connections = [] for _ in range(100): 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): pool = ConnectionPool([(x, {}) for x in range(100)]) @@ -53,23 +68,37 @@ def test_connection_is_skipped_when_dead(self): pool = ConnectionPool([(x, {}) for x in range(2)]) 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_new_connection_is_not_marked_dead(self): + # Create 10 connections + pool = ConnectionPool([(Connection(), {}) for _ in range(10)]) + + # Pass in a new connection that is not in the pool to mark as dead + new_connection = Connection() + pool.mark_dead(new_connection) + + # Nothing should be marked dead + self.assertEquals(0, len(pool.dead_count)) def test_connection_is_forcibly_resurrected_when_no_live_ones_are_availible(self): pool = ConnectionPool([(x, {}) for x in range(2)]) pool.dead_count[0] = 1 - pool.mark_dead(0) # failed twice, longer timeout - pool.mark_dead(1) # failed the first time, first to be resurrected + pool.mark_dead(0) # failed twice, longer timeout + pool.mark_dead(1) # failed the first time, first to be resurrected self.assertEquals([], pool.connections) 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): pool = ConnectionPool([(x, {}) for x in range(100)]) now = time.time() - pool.mark_dead(42, now=now-61) + pool.mark_dead(42, now=now - 61) pool.get_connection() self.assertEquals(42, pool.connections[-1]) self.assertEquals(100, len(pool.connections)) @@ -89,7 +118,7 @@ def test_already_failed_connection_has_longer_timeout(self): pool.mark_dead(42, now=now) 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): pool = ConnectionPool([(x, {}) for x in range(100)]) @@ -98,7 +127,7 @@ def test_timeout_for_failed_connections_is_limitted(self): pool.mark_dead(42, now=now) 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): pool = ConnectionPool([(x, {}) for x in range(100)]) @@ -109,4 +138,3 @@ def test_dead_count_is_wiped_clean_for_connection_if_marked_live(self): self.assertEquals(3, pool.dead_count[42]) pool.mark_live(42) self.assertNotIn(42, pool.dead_count) - diff --git a/test_elasticsearch/test_exceptions.py b/test_elasticsearch/test_exceptions.py index 985518de9..0bb0fe8b2 100644 --- a/test_elasticsearch/test_exceptions.py +++ b/test_elasticsearch/test_exceptions.py @@ -5,19 +5,22 @@ class TestTransformError(TestCase): def test_transform_error_parse_with_error_reason(self): - e = TransportError(500, 'InternalServerError', { - 'error': { - 'root_cause': [ - {"type": "error", "reason": "error reason"} - ] - } - }) + e = TransportError( + 500, + "InternalServerError", + {"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): - e = TransportError(500, 'InternalServerError', { - 'error': 'something error message' - }) + e = TransportError( + 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')", + ) diff --git a/test_elasticsearch/test_serializer.py b/test_elasticsearch/test_serializer.py index 3c7925e51..0309c5f1d 100644 --- a/test_elasticsearch/test_serializer.py +++ b/test_elasticsearch/test_serializer.py @@ -5,30 +5,44 @@ from datetime import datetime 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 .test_cases import TestCase, SkipTest + class TestJSONSerializer(TestCase): 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): if sys.version_info[:2] == (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): - 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): self.assertRaises(SerializationError, JSONSerializer().dumps, object()) def test_raises_serialization_error_on_load_error(self): 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): self.assertEquals("你好", JSONSerializer().dumps("你好")) @@ -51,11 +65,18 @@ def test_deserializes_json_by_default(self): self.assertEquals({"some": "data"}, self.de.loads('{"some":"data"}')) def test_deserializes_text_with_correct_ct(self): - self.assertEquals('{"some":"data"}', self.de.loads('{"some":"data"}', 'text/plain')) - self.assertEquals('{"some":"data"}', self.de.loads('{"some":"data"}', 'text/plain; charset=whatever')) + self.assertEquals( + '{"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): - 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, {}) diff --git a/test_elasticsearch/test_server/__init__.py b/test_elasticsearch/test_server/__init__.py index 9fb387895..53adbe0fb 100644 --- a/test_elasticsearch/test_server/__init__.py +++ b/test_elasticsearch/test_server/__init__.py @@ -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 + def get_client(**kwargs): global client 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: from test_elasticsearch.local import get_client as local_get_client + new_client = local_get_client(**kwargs) except ImportError: # fallback to using vanilla client @@ -24,6 +29,7 @@ def get_client(**kwargs): def setup(): get_client() + class ElasticsearchTestCase(BaseTestCase): @staticmethod def _get_client(**kwargs): diff --git a/test_elasticsearch/test_server/test_client.py b/test_elasticsearch/test_server/test_client.py index 70393fd66..ed79cb4ee 100644 --- a/test_elasticsearch/test_server/test_client.py +++ b/test_elasticsearch/test_server/test_client.py @@ -3,6 +3,7 @@ from . import ElasticsearchTestCase + class TestUnicode(ElasticsearchTestCase): def test_indices_analyze(self): self.client.indices.analyze(body='{"text": "привет"}') diff --git a/test_elasticsearch/test_server/test_helpers.py b/test_elasticsearch/test_server/test_helpers.py index 81fc4f220..3a470f080 100644 --- a/test_elasticsearch/test_server/test_helpers.py +++ b/test_elasticsearch/test_server/test_helpers.py @@ -306,24 +306,66 @@ def test_errors_are_collected_properly(self): self.assertEquals(1, success) self.assertEquals(1, failed) + def test_return_docs_when_not_raising_errors(self): + bulk_load_index_name = "test_bulk_load" + mappings = { + "properties": { + "test_field": {"type": "integer"} + } + } + self.client.indices.create(bulk_load_index_name, body={"mappings": mappings}) + docs = [{"test_field": 1}, + {"test_field": "string_error"}, + {"test_field": "second_string_error"}, + {"test_field": 5}, + {"test_field": 6} + ] + success, errors = helpers.bulk(self.client, docs, raise_on_error=False, + index=bulk_load_index_name) + self.assertEqual(success, 3) + self.assertEqual(len(errors), 2) + self.assertEqual(errors[0]["index"]["data"], {"test_field": "string_error"}) + self.assertEqual(errors[1]["index"]["data"], {"test_field": "second_string_error"}) + + def test_return_docs_when_not_raising_errors_passing_docs_as_strings(self): + bulk_load_index_name = "test_bulk_load_string_docs" + mappings = { + "properties": { + "test_field": {"type": "integer"} + } + } + self.client.indices.create(bulk_load_index_name, body={"mappings": mappings}) + docs = ['{"test_field": 1}', + '{"test_field": "string_error"}', + '{"test_field": "second_string_error"}', + '{"test_field": 5}', + '{"test_field": 6}' + ] + success, errors = helpers.bulk(self.client, docs, raise_on_error=False, + index=bulk_load_index_name) + self.assertEqual(success, 3) + self.assertEqual(len(errors), 2) + self.assertEqual(errors[0]["index"]["data"], '{"test_field": "string_error"}') + self.assertEqual(errors[1]["index"]["data"], '{"test_field": "second_string_error"}') + class TestScan(ElasticsearchTestCase): mock_scroll_responses = [ { - '_scroll_id': 'dummy_id', - '_shards': {'successful': 4, 'total': 5}, - 'hits': {'hits': [{'scroll_data': 42}]}, + "_scroll_id": "dummy_id", + "_shards": {"successful": 4, "total": 5, "skipped": 0}, + "hits": {"hits": [{"scroll_data": 42}]}, }, { - '_scroll_id': 'dummy_id', - '_shards': {'successful': 4, 'total': 5}, - 'hits': {'hits': []}, + "_scroll_id": "dummy_id", + "_shards": {"successful": 4, "total": 5, "skipped": 0}, + "hits": {"hits": []}, }, ] @classmethod 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() def test_order_can_be_preserved(self): @@ -366,87 +408,101 @@ def test_scroll_error(self): bulk.append({"value": x}) 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 - data = list(helpers.scan( - self.client, - index='test_index', - size=2, - raise_on_error=False, - clear_scroll=False - )) + data = list( + helpers.scan( + self.client, + index="test_index", + size=2, + raise_on_error=False, + clear_scroll=False, + ) + ) 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 with self.assertRaises(ScanError): - data = list(helpers.scan( - self.client, - index='test_index', - size=2, - raise_on_error=True, - clear_scroll=False - )) + data = list( + helpers.scan( + self.client, + index="test_index", + size=2, + raise_on_error=True, + clear_scroll=False, + ) + ) 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): - with patch.object(self, 'client') as client_mock: + with patch.object(self, "client") as client_mock: client_mock.search.return_value = { - '_scroll_id': 'dummy_id', - '_shards': {'successful': 4, 'total': 5}, - 'hits': {'hits': [{'search_data': 1}]}, + "_scroll_id": "dummy_id", + "_shards": {"successful": 4, "total": 5, "skipped": 0}, + "hits": {"hits": [{"search_data": 1}]}, } client_mock.scroll.side_effect = self.mock_scroll_responses - data = list(helpers.scan(self.client, index='test_index', size=2, raise_on_error=False)) - self.assertEqual(data, [{'search_data': 1}, {'scroll_data': 42}]) + data = list( + 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 with self.assertRaises(ScanError): 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() def test_no_scroll_id_fast_route(self): - with patch.object(self, 'client') as client_mock: - client_mock.search.return_value = {'no': '_scroll_id'} - data = list(helpers.scan(self.client, index='test_index')) + with patch.object(self, "client") as client_mock: + client_mock.search.return_value = {"no": "_scroll_id"} + data = list(helpers.scan(self.client, index="test_index")) self.assertEqual(data, []) client_mock.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): bulk = [] for x in range(4): - bulk.append({'index': {'_index': 'test_index', '_type': '_doc'}}) - bulk.append({'value': x}) + bulk.append({"index": {"_index": "test_index", "_type": "_doc"}}) + bulk.append({"value": x}) 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 - list(helpers.scan( - self.client, - index='test_index', - size=2, - raise_on_error=False, - clear_scroll=False - )) + list( + helpers.scan( + self.client, + index="test_index", + size=2, + raise_on_error=False, + clear_scroll=False, + ) + ) logger_mock.warning.assert_called() scroll_mock.side_effect = self.mock_scroll_responses try: - list(helpers.scan( - self.client, - index='test_index', - size=2, - raise_on_error=True, - clear_scroll=False - )) + list( + helpers.scan( + self.client, + index="test_index", + size=2, + raise_on_error=True, + clear_scroll=False, + ) + ) except ScanError: pass logger_mock.warning.assert_called() @@ -454,20 +510,28 @@ def test_logger(self, logger_mock): def test_clear_scroll(self): bulk = [] for x in range(4): - bulk.append({'index': {'_index': 'test_index', '_type': '_doc'}}) - bulk.append({'value': x}) + bulk.append({"index": {"_index": "test_index", "_type": "_doc"}}) + bulk.append({"value": x}) self.client.bulk(bulk, refresh=True) - with patch.object(self.client, 'clear_scroll', wraps=self.client.clear_scroll) as spy: - list(helpers.scan(self.client, index='test_index', size=2)) + with patch.object( + 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.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.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() diff --git a/test_elasticsearch/test_transport.py b/test_elasticsearch/test_transport.py index 50acb7fae..a1fbba9d2 100644 --- a/test_elasticsearch/test_transport.py +++ b/test_elasticsearch/test_transport.py @@ -9,11 +9,12 @@ from .test_cases import TestCase + class DummyConnection(Connection): def __init__(self, **kwargs): - self.exception = kwargs.pop('exception', None) - self.status, self.data = kwargs.pop('status', 200), kwargs.pop('data', '{}') - self.headers = kwargs.pop('headers', {}) + self.exception = kwargs.pop("exception", None) + self.status, self.data = kwargs.pop("status", 200), kwargs.pop("data", "{}") + self.headers = kwargs.pop("headers", {}) self.calls = [] super(DummyConnection, self).__init__(**kwargs) @@ -23,7 +24,8 @@ def perform_request(self, *args, **kwargs): raise self.exception return self.status, self.headers, self.data -CLUSTER_NODES = '''{ + +CLUSTER_NODES = """{ "_nodes" : { "total" : 1, "successful" : 1, @@ -46,18 +48,48 @@ def perform_request(self, *args, **kwargs): } } } -}''' +}""" + +CLUSTER_NODES_7x_PUBLISH_HOST = """{ + "_nodes" : { + "total" : 1, + "successful" : 1, + "failed" : 0 + }, + "cluster_name" : "elasticsearch", + "nodes" : { + "SRZpKFZdQguhhvifmN6UVA" : { + "name" : "SRZpKFZ", + "transport_address" : "127.0.0.1:9300", + "host" : "127.0.0.1", + "ip" : "127.0.0.1", + "version" : "5.0.0", + "build_hash" : "253032b", + "roles" : [ "master", "data", "ingest" ], + "http" : { + "bound_address" : [ "[fe80::1]:9200", "[::1]:9200", "127.0.0.1:9200" ], + "publish_address" : "somehost.tld/1.1.1.1:123", + "max_content_length_in_bytes" : 104857600 + } + } + } +}""" + class TestHostsInfoCallback(TestCase): def test_master_only_nodes_are_ignored(self): nodes = [ - {'roles': [ "master"]}, - {'roles': [ "master", "data", "ingest"]}, - {'roles': [ "data", "ingest"]}, - {'roles': [ ]}, - {} + {"roles": ["master"]}, + {"roles": ["master", "data", "ingest"]}, + {"roles": ["data", "ingest"]}, + {"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) @@ -65,57 +97,85 @@ class TestTransport(TestCase): def test_single_connection_uses_dummy_connection_pool(self): t = Transport([{}]) self.assertIsInstance(t.connection_pool, DummyConnectionPool) - t = Transport([{'host': 'localhost'}]) + t = Transport([{"host": "localhost"}]) self.assertIsInstance(t.connection_pool, DummyConnectionPool) def test_request_timeout_extracted_from_params_and_passed(self): 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(('GET', '/', {}, None), t.get_connection().calls[0][0]) - self.assertEquals({'timeout': 42, 'ignore': (), 'headers': None}, t.get_connection().calls[0][1]) + self.assertEquals(("GET", "/", {}, None), t.get_connection().calls[0][0]) + self.assertEquals( + { + "timeout": 42, + "ignore": (), + "headers": None, + }, + t.get_connection().calls[0][1], + ) + + def test_request_with_custom_user_agent_header(self): + t = Transport([{}], connection_class=DummyConnection) + + t.perform_request("GET", "/", headers={"user-agent": "my-custom-value/1.2.3"}) + self.assertEquals(1, len(t.get_connection().calls)) + self.assertEquals( + {"timeout": None, "ignore": (), "headers": {"user-agent": "my-custom-value/1.2.3"} + }, + t.get_connection().calls[0][1], + ) 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(('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): - 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(('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): 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(('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): t = Transport([{}], connection_class=DummyConnection) - body = b'\xe4\xbd\xa0\xe5\xa5\xbd' - t.perform_request('GET', '/', body=body) + body = b"\xe4\xbd\xa0\xe5\xa5\xbd" + t.perform_request("GET", "/", body=body) 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): 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(('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): - t = Transport([{'host': 'google.com'}], port=123) + t = Transport([{"host": "google.com"}], port=123) 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): dt = object() @@ -126,6 +186,7 @@ def test_custom_connection_class(self): class MyConnection(object): def __init__(self, **kwargs): self.kwargs = kwargs + t = Transport([{}], connection_class=MyConnection) self.assertEquals(1, len(t.connection_pool.connections)) self.assertIsInstance(t.connection_pool.connections[0], MyConnection) @@ -135,18 +196,26 @@ def test_add_connection(self): t.add_connection({"host": "google.com", "port": 1234}) 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): - 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)) 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)) def test_resurrected_connection_will_be_marked_as_live_on_success(self): @@ -156,35 +225,57 @@ def test_resurrected_connection_will_be_marked_as_live_on_success(self): t.connection_pool.mark_dead(con1) 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.dead_count)) def test_sniff_will_use_seed_connections(self): - t = Transport([{'data': CLUSTER_NODES}], connection_class=DummyConnection) - t.set_connections([{'data': 'invalid'}]) + t = Transport([{"data": CLUSTER_NODES}], connection_class=DummyConnection) + t.set_connections([{"data": "invalid"}]) t.sniff_hosts() 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): - 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('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): - t = Transport([{'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]) + t = Transport( + [{"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): - 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() - 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): - 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] t.sniff_hosts() @@ -192,25 +283,45 @@ def test_sniff_reuses_connection_instances_if_possible(self): self.assertIs(connection, t.get_connection()) def test_sniff_on_fail_triggers_sniffing_on_fail(self): - t = Transport([{'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', '/') + t = Transport( + [{"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.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): - t = Transport([{"data": CLUSTER_NODES}], - connection_class=DummyConnection, sniffer_timeout=5) + t = Transport( + [{"data": CLUSTER_NODES}], + connection_class=DummyConnection, + sniffer_timeout=5, + ) for _ in range(4): - t.perform_request('GET', '/') + t.perform_request("GET", "/") self.assertEquals(1, len(t.connection_pool.connections)) self.assertIsInstance(t.get_connection(), DummyConnection) 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('http://1.1.1.1:123', t.get_connection().host) - self.assertTrue(time.time() - 1 < t.last_sniff < time.time() + 0.01 ) - + self.assertEquals("http://1.1.1.1:123", t.get_connection().host) + self.assertTrue(time.time() - 1 < t.last_sniff < time.time() + 0.01) + + def test_sniff_7x_publish_host(self): + # Test the response shaped when a 7.x node has publish_host set + # and the returend data is shaped in the fqdn/ip:port format. + t = Transport( + [{"data": CLUSTER_NODES_7x_PUBLISH_HOST}], + connection_class=DummyConnection, + sniff_timeout=42, + ) + t.sniff_hosts() + # Ensure we parsed out the fqdn and port from the fqdn/ip:port string. + self.assertEqual(t.connection_pool.connection_opts[0][1], + {'host': 'somehost.tld', 'port': 123}) diff --git a/tox.ini b/tox.ini index 860dc6369..b8cb4eacc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = pypy,py26,py27,py33,py34,py35,py36 +envlist = pypy,py26,py27,py33,py34,py35,py36,py37 [testenv] whitelist_externals = git setenv =