├── .ci
├── .gitignore
└── make.sh
├── setup.cfg
├── dev-requirements.txt
├── .dockerignore
├── assets
└── elastic-enterprise-search-logo.png
├── .buildkite
├── pull-requests.json
├── Dockerfile
├── pipeline.yml
├── run-tests
├── certs
│ ├── testnode.crt
│ ├── ca.crt
│ ├── testnode_no_san.crt
│ ├── ca.key
│ ├── testnode.key
│ ├── testnode_no_san.key
│ └── README.md
├── functions
│ ├── wait-for-container.sh
│ ├── imports.sh
│ └── cleanup.sh
├── run-repository.sh
├── run-enterprise-search.sh
└── run-elasticsearch.sh
├── MANIFEST.in
├── docs
└── guide
│ ├── release-notes
│ ├── 8-11-0.asciidoc
│ ├── 8-4-0.asciidoc
│ ├── 7-17-0.asciidoc
│ ├── 7-12-0.asciidoc
│ ├── 8-10-0.asciidoc
│ ├── index.asciidoc
│ ├── 7-14-0.asciidoc
│ ├── 8-3-0.asciidoc
│ ├── 7-16-0.asciidoc
│ ├── 7-13-0.asciidoc
│ ├── 8-18-0.asciidoc
│ ├── 7-11-0.asciidoc
│ ├── 7-15-0.asciidoc
│ ├── 7-10-0.asciidoc
│ └── 8-2-0.asciidoc
│ ├── index.asciidoc
│ ├── installation.asciidoc
│ ├── overview.asciidoc
│ └── enterprise-search-api.asciidoc
├── .github
└── workflows
│ ├── unified-release.yml
│ ├── backport.yml
│ └── ci.yml
├── tests
├── __init__.py
├── server
│ ├── __init__.py
│ ├── async_
│ │ ├── __init__.py
│ │ ├── conftest.py
│ │ └── test_app_search.py
│ ├── test_httpbin.py
│ ├── conftest.py
│ └── test_enterprise_search.py
├── client
│ ├── app_search
│ │ ├── __init__.py
│ │ ├── conftest.py
│ │ ├── cassettes
│ │ │ ├── test_delete_documents.yaml
│ │ │ ├── test_search_es_search.yaml
│ │ │ ├── test_list_engines.yaml
│ │ │ ├── test_query_suggestions.yaml
│ │ │ ├── test_not_authorized.yaml
│ │ │ ├── test_index_documents.yaml
│ │ │ ├── test_list_documents.yaml
│ │ │ └── test_search.yaml
│ │ └── test_search_es_search.py
│ ├── enterprise_search
│ │ ├── __init__.py
│ │ ├── cassettes
│ │ │ ├── test_get_version.yaml
│ │ │ ├── test_get_health.yaml
│ │ │ ├── test_get_and_put_read_only.yaml
│ │ │ └── test_get_stats.yaml
│ │ └── test_enterprise_search.py
│ ├── workplace_search
│ │ ├── __init__.py
│ │ └── cassettes
│ │ │ ├── test_index_documents_content_source_not_found.yaml
│ │ │ ├── test_index_documents.yaml
│ │ │ ├── test_oauth_exchange_for_access_token_code.yaml
│ │ │ ├── test_oauth_exchange_for_access_token_refresh_token.yaml
│ │ │ └── test_oauth_exchange_for_access_token_invalid_grant.yaml
│ ├── test_options.py
│ ├── test_enterprise_search.py
│ ├── test_client_meta.py
│ └── test_auth.py
├── test_package.py
├── utils.py
├── test_serializer.py
├── conftest.py
├── test_oauth.py
└── test_utils.py
├── elastic_enterprise_search
├── _async
│ ├── __init__.py
│ └── client
│ │ └── enterprise_search.py
├── _sync
│ ├── __init__.py
│ └── client
│ │ └── enterprise_search.py
├── _version.py
├── _serializer.py
├── exceptions.py
└── __init__.py
├── catalog-info.yaml
├── utils
├── run-unasync.py
├── bump-version.py
├── license-headers.py
└── build-dists.py
├── CONTRIBUTING.md
├── .gitignore
├── README.md
├── noxfile.py
└── setup.py
/.ci/.gitignore:
--------------------------------------------------------------------------------
1 | tmp/
2 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [isort]
2 | profile = black
3 |
--------------------------------------------------------------------------------
/dev-requirements.txt:
--------------------------------------------------------------------------------
1 | build
2 | nox
3 | twine
4 | unasync
5 | aiohttp
6 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | docs
2 | example
3 | venv
4 | .git
5 | .tox
6 | .nox
7 | .*_cache
8 |
--------------------------------------------------------------------------------
/assets/elastic-enterprise-search-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elastic/enterprise-search-python/HEAD/assets/elastic-enterprise-search-logo.png
--------------------------------------------------------------------------------
/.buildkite/pull-requests.json:
--------------------------------------------------------------------------------
1 | {
2 | "jobs": [
3 | {
4 | "enabled": true,
5 | "pipeline_slug": "enterprise-search-python-test",
6 | "allow_org_users": true
7 | }
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include MANIFEST.in
3 | include README.md
4 | include setup.py
5 |
6 | prune docs/_build
7 | prune tests
8 | recursive-exclude * __pycache__
9 | recursive-exclude * *.py[co]
10 |
--------------------------------------------------------------------------------
/docs/guide/release-notes/8-11-0.asciidoc:
--------------------------------------------------------------------------------
1 | [[release-notes-8-11-0]]
2 | === 8.10.0 Release Notes
3 |
4 | Client is compatible with Elastic Enterprise Search 8.11.0
5 |
6 | [discrete]
7 | ==== Added
8 |
9 | - Added supported for Python 3.12.
--------------------------------------------------------------------------------
/docs/guide/release-notes/8-4-0.asciidoc:
--------------------------------------------------------------------------------
1 | [[release-notes-8-4-0]]
2 | === 8.4.0 Release Notes
3 |
4 | [discrete]
5 | ==== Added
6 |
7 | - Added the `app_search.search_es_search` API method.
8 |
9 | [discrete]
10 | ==== Changed
11 |
12 | - Changed URL parsing to use default ports for `http` and `https` schemes instead of raising an error.
13 |
--------------------------------------------------------------------------------
/.buildkite/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG PYTHON_VERSION=3.8
2 | FROM python:${PYTHON_VERSION}
3 |
4 | WORKDIR /code/enterprise-search-python
5 |
6 | COPY dev-requirements.txt .
7 |
8 | ENV AIOHTTP_NO_EXTENSIONS=1
9 |
10 | RUN python -m pip install \
11 | --disable-pip-version-check \
12 | --no-cache-dir \
13 | -r dev-requirements.txt
14 |
15 | COPY . .
16 |
--------------------------------------------------------------------------------
/docs/guide/release-notes/7-17-0.asciidoc:
--------------------------------------------------------------------------------
1 | [[release-notes-7-17-0]]
2 | === 7.17.0 Release Notes
3 |
4 | [discrete]
5 | ==== General
6 |
7 | - Updated APIs to the 7.17 specification
8 |
9 | [discrete]
10 | ==== App Search
11 |
12 | - Added the `current_page` and `page_size` parameters to the `list_crawler_crawl_requests` and `list_crawler_process_crawls` APIs
13 |
--------------------------------------------------------------------------------
/docs/guide/release-notes/7-12-0.asciidoc:
--------------------------------------------------------------------------------
1 | [[release-notes-7-12-0]]
2 | === 7.12.0 Release Notes
3 |
4 | [discrete]
5 | ==== General
6 |
7 | - Updated APIs to the 7.12 specification
8 | - Fixed encoding of objects in query string to match Ruby on Rails
9 |
10 | [discrete]
11 | ==== Workplace Search
12 |
13 | - Added `oauth_authorize_url()` and `oauth_exchange_for_access_token()`
14 | helper methods for implementing OAuth authentication.
15 |
--------------------------------------------------------------------------------
/docs/guide/index.asciidoc:
--------------------------------------------------------------------------------
1 | = enterprise-search-python
2 |
3 | :doctype: book
4 |
5 | include::{asciidoc-dir}/../../shared/attributes.asciidoc[]
6 |
7 | include::overview.asciidoc[]
8 |
9 | include::installation.asciidoc[]
10 |
11 | include::connecting.asciidoc[]
12 |
13 | include::app-search-api.asciidoc[]
14 |
15 | include::workplace-search-api.asciidoc[]
16 |
17 | include::enterprise-search-api.asciidoc[]
18 |
19 | include::release-notes/index.asciidoc[]
20 |
--------------------------------------------------------------------------------
/docs/guide/release-notes/8-10-0.asciidoc:
--------------------------------------------------------------------------------
1 | [[release-notes-8-10-0]]
2 | === 8.10.0 Release Notes
3 |
4 | Client is compatible with Elastic Enterprise Search 8.10.0
5 |
6 | [discrete]
7 | ==== Added
8 |
9 | - Added `get_storage`, `get_stale_storage` and `delete_stale_storage` to `enterprise_search`.
10 | - Added `precision_enabled` parameter name to `app_search.put_search_settings`.
11 |
12 |
13 | [discrete]
14 | ==== Fixed
15 |
16 | - Fixed `boosts` parameter name in `app_search.search` and `app_search.search_explain`.
--------------------------------------------------------------------------------
/.buildkite/pipeline.yml:
--------------------------------------------------------------------------------
1 | ---
2 | steps:
3 | - label: ":python: {{ matrix.python }} :elastic-enterprise-search: {{ matrix.stack_version }}"
4 | agents:
5 | provider: gcp
6 | matrix:
7 | setup:
8 | python:
9 | - "3.7"
10 | - "3.8"
11 | - "3.9"
12 | - "3.10"
13 | - "3.11"
14 | - "3.12"
15 | stack_version:
16 | - "8.18.0-SNAPSHOT"
17 | env:
18 | PYTHON_VERSION: "{{ matrix.python }}"
19 | STACK_VERSION: "{{ matrix.stack_version }}"
20 | command: ./.buildkite/run-tests
21 |
--------------------------------------------------------------------------------
/docs/guide/release-notes/index.asciidoc:
--------------------------------------------------------------------------------
1 | [[release_notes]]
2 | == Release Notes
3 |
4 | * <>
5 | * <>
6 | * <>
7 | * <>
8 | * <>
9 | * <>
10 |
11 | include::8-18-0.asciidoc[]
12 | include::8-11-0.asciidoc[]
13 | include::8-10-0.asciidoc[]
14 | include::8-4-0.asciidoc[]
15 | include::8-3-0.asciidoc[]
16 | include::8-2-0.asciidoc[]
17 |
--------------------------------------------------------------------------------
/.github/workflows/unified-release.yml:
--------------------------------------------------------------------------------
1 | name: Unified Release
2 |
3 | on:
4 | pull_request:
5 | paths-ignore:
6 | - 'README.md'
7 | push:
8 | paths-ignore:
9 | - 'README.md'
10 | branches:
11 | - main
12 | - master
13 | - '[0-9]+.[0-9]+'
14 | - '[0-9]+.x'
15 |
16 | jobs:
17 | assemble:
18 | name: Assemble
19 | runs-on: ubuntu-latest
20 | env:
21 | STACK_VERSION: "8.18-SNAPSHOT"
22 | steps:
23 | - name: Checkout
24 | uses: actions/checkout@v2
25 | - name: "Assemble ${{ env.STACK_VERSION }}"
26 | run: "./.ci/make.sh assemble ${{ env.STACK_VERSION }}"
27 |
--------------------------------------------------------------------------------
/docs/guide/release-notes/7-14-0.asciidoc:
--------------------------------------------------------------------------------
1 | [[release-notes-7-14-0]]
2 | === 7.14.0 Release Notes
3 |
4 | [discrete]
5 | ==== General
6 |
7 | - Updated APIs to the 7.14 specification
8 |
9 | [discrete]
10 | ==== App Search
11 |
12 | - Added the `create_api_key`, `delete_api_key`, `get_api_key`, `put_api_key`,
13 | and `list_api_keys` APIs
14 |
15 |
16 | [discrete]
17 | ==== Workplace Search
18 |
19 | - Added the `create_batch_synonym_sets`, `command_sync_jobs`, `put_content_source_icons`,
20 | `get_current_user`, and `delete_documents_by_query`, `delete_synonym_set`,
21 | `get_synonym_set`, `put_synonym_set`, and `list_synonym_sets` APIs
22 |
--------------------------------------------------------------------------------
/docs/guide/release-notes/8-3-0.asciidoc:
--------------------------------------------------------------------------------
1 | [[release-notes-8-3-0]]
2 | === 8.3.0 Release Notes
3 |
4 | [discrete]
5 | ==== Added
6 |
7 | - Added the `current_page` parameter to many APIs that support pagination.
8 | - Added the `app_search.multi_search` API for v8.x
9 | - Added the `enterprise_search.get_search_engines` API
10 |
11 | [discrete]
12 | ==== Fixed
13 |
14 | - Fixed the `overrides` parameter of the `app_search.get_top_queries_analytics` and `create_crawler_crawl_request` APIs
15 |
16 |
17 | [discrete]
18 | ==== Removed
19 |
20 | - Removed unused `created_at` parameters for various `create_*` APIs. These parameters weren't used by the server and were only generated due to issues with the API specification.
21 |
--------------------------------------------------------------------------------
/docs/guide/installation.asciidoc:
--------------------------------------------------------------------------------
1 | [[installation]]
2 | == Installation
3 |
4 | The Python client for Enterprise Search can be installed with pip:
5 |
6 | [source,sh]
7 | -------------------------------------------------
8 | $ python -m pip install elastic-enterprise-search
9 | -------------------------------------------------
10 |
11 | [NOTE]
12 | The package `elastic-enterprise-search` was previously used as a client for
13 | only 'Elastic Workplace Search' before the product was renamed. When installing
14 | make sure you receive a version greater than 7.10.0
15 |
16 | [discrete]
17 | === Compatibility
18 |
19 | Language clients are forward compatible; meaning that clients support communicating
20 | with greater or equal minor versions of Elastic Enterprise Search.
21 |
--------------------------------------------------------------------------------
/docs/guide/release-notes/7-16-0.asciidoc:
--------------------------------------------------------------------------------
1 | [[release-notes-7-16-0]]
2 | === 7.16.0 Release Notes
3 |
4 | [discrete]
5 | ==== General
6 |
7 | - Deprecated support for Python 2.7, 3.4, and 3.5. Support will be removed in v8.0.0.
8 | - Updated APIs to the 7.16 specification
9 |
10 | [discrete]
11 | ==== App Search
12 |
13 | - Added the `get_adaptive_relevance_settings`, `put_adaptive_relevance_settings`, `get_adaptive_relevance_suggestions`, `list_adaptive_relevance_suggestions`, `put_adaptive_relevance_suggestions`
14 | - Fixed the pagination parameters for `list_crawler_crawl_requests` and `list_crawler_process_crawls` APIs to `current_page` and `page_size`, were previously `limit`.
15 |
16 |
17 | [discrete]
18 | ==== Workplace Search
19 |
20 | - Added the `list_documents` API
21 |
--------------------------------------------------------------------------------
/docs/guide/release-notes/7-13-0.asciidoc:
--------------------------------------------------------------------------------
1 | [[release-notes-7-13-0]]
2 | === 7.13.0 Release Notes
3 |
4 | [discrete]
5 | ==== General
6 |
7 | - Updated APIs to the 7.13 specification
8 |
9 | [discrete]
10 | ==== Workplace Search
11 |
12 | - The client now supports Basic Authentication and Elasticsearch tokens.
13 | All Workplace Search APIs support Basic Authentication, Elasticsearch tokens
14 | and Workplace Search admin user access tokens as an authentication method.
15 | You still need to set up user access tokens generated by the Workplace Search OAuth
16 | Service for the Search API and the Analytics Events API.
17 |
18 | - Added the `get_document`, `delete_all_documents`, `get_content_source`,
19 | `create_content_source`, `delete_content_source`, `list_content_sources`,
20 | and `put_content_source` APIs.
21 |
--------------------------------------------------------------------------------
/.github/workflows/backport.yml:
--------------------------------------------------------------------------------
1 | name: Backport
2 | on:
3 | pull_request_target:
4 | types:
5 | - closed
6 | - labeled
7 |
8 | jobs:
9 | backport:
10 | name: Backport
11 | runs-on: ubuntu-latest
12 | # Only react to merged PRs for security reasons.
13 | # See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.
14 | if: >
15 | github.event.pull_request.merged
16 | && (
17 | github.event.action == 'closed'
18 | || (
19 | github.event.action == 'labeled'
20 | && contains(github.event.label.name, 'backport')
21 | )
22 | )
23 | steps:
24 | - uses: tibdex/backport@9565281eda0731b1d20c4025c43339fb0a23812e # v2.0.4
25 | with:
26 | github_token: ${{ secrets.GITHUB_TOKEN }}
27 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
--------------------------------------------------------------------------------
/tests/server/__init__.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
--------------------------------------------------------------------------------
/tests/client/app_search/__init__.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
--------------------------------------------------------------------------------
/tests/server/async_/__init__.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
--------------------------------------------------------------------------------
/elastic_enterprise_search/_async/__init__.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
--------------------------------------------------------------------------------
/elastic_enterprise_search/_sync/__init__.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
--------------------------------------------------------------------------------
/tests/client/enterprise_search/__init__.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
--------------------------------------------------------------------------------
/tests/client/workplace_search/__init__.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
--------------------------------------------------------------------------------
/elastic_enterprise_search/_version.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | __version__ = "8.18.0"
19 |
--------------------------------------------------------------------------------
/.buildkite/run-tests:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #
3 | # Version 1.1
4 | # - Moved to .buildkite folder and separated out `run-repository.sh`
5 | # - Add `$RUNSCRIPTS` env var for running Elasticsearch dependent products
6 |
7 | # Default environment variables
8 | export PYTHON_VERSION=${PYTHON_VERSION-3.9}
9 | export RUNSCRIPTS=enterprise-search
10 | export TEST_SUITE=platinum
11 |
12 | script_path=$(dirname $(realpath -s $0))
13 | source $script_path/functions/imports.sh
14 | set -euo pipefail
15 |
16 | echo "--- :python: :elastic-enterprise-search: Start $STACK_VERSION container"
17 | DETACH=true bash .buildkite/run-elasticsearch.sh
18 |
19 | if [[ -n "$RUNSCRIPTS" ]]; then
20 | for RUNSCRIPT in ${RUNSCRIPTS//,/ } ; do
21 | echo -e "--- Running run-$RUNSCRIPT.sh"
22 | CONTAINER_NAME=${RUNSCRIPT} \
23 | DETACH=true \
24 | bash .buildkite/run-${RUNSCRIPT}.sh
25 | done
26 | fi
27 |
28 | echo -e "--- Run repository specific tests"
29 | bash .buildkite/run-repository.sh
30 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | ---
2 | name: CI
3 |
4 | on: [push, pull_request]
5 |
6 | jobs:
7 | package:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout Repository
11 | uses: actions/checkout@v2
12 | - name: Set up Python 3.x
13 | uses: actions/setup-python@v2
14 | with:
15 | python-version: "3.12"
16 | - name: Install dependencies
17 | run: |
18 | python3 -m pip install build twine
19 | - name: Build packages
20 | run: |
21 | python3 -m build
22 | - name: Check packages
23 | run: |
24 | set -exo pipefail;
25 | if [ $(python3 -m twine check dist/* | grep -c 'warning') != 0 ]; then exit 1; fi
26 |
27 | lint:
28 | runs-on: ubuntu-latest
29 | steps:
30 | - name: Checkout Repository
31 | uses: actions/checkout@v2
32 | - name: Set up Python 3.x
33 | uses: actions/setup-python@v2
34 | with:
35 | python-version: "3.12"
36 | - name: Install dependencies
37 | run: |
38 | python3 -m pip install nox
39 | - name: Lint the code
40 | run: nox -s lint
41 |
--------------------------------------------------------------------------------
/tests/client/app_search/conftest.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import pytest
19 |
20 | from elastic_enterprise_search import AppSearch
21 |
22 |
23 | @pytest.fixture()
24 | def app_search():
25 | yield AppSearch(
26 | "https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io:443",
27 | bearer_auth="private-ybzoyx7cok65hpxyxkwaarnn",
28 | )
29 |
--------------------------------------------------------------------------------
/tests/test_package.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import elastic_enterprise_search
19 | from elastic_enterprise_search import __all__, _utils
20 |
21 |
22 | def test_all_is_sorted():
23 | assert elastic_enterprise_search.__all__ == sorted(
24 | elastic_enterprise_search.__all__
25 | )
26 | assert _utils.__all__ == sorted(_utils.__all__)
27 | assert __all__ == sorted(__all__)
28 |
--------------------------------------------------------------------------------
/.buildkite/certs/testnode.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDIjCCAgqgAwIBAgIUdFXN1j/ZlZrYcgE4NuUWYk1qJBEwDQYJKoZIhvcNAQEL
3 | BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l
4 | cmF0ZWQgQ0EwHhcNMjMxMDEwMDU1OTU5WhcNMjYxMDA5MDU1OTU5WjATMREwDwYD
5 | VQQDEwhpbnN0YW5jZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMlU
6 | XnIAKxkACS4KWm1+xN3jy+n3Wq034jNdPiWEOwM2jX2p6gMOZbwaybMKnCPwcXEe
7 | uzviujUkmY8GeaBGOBSt8fu0L6Ew3EMeFF3sAE3FBYyEWS9k/1A0IS+PtleTPRWn
8 | TwKEnQPe36V39aKjUxLgZ/yCryB0PW44LM7vzZLMZ9agBQrmYHFUancJwB65Irv9
9 | zEQjyn5jjj2OFPb2P1qln6h51xOA/0w5HUXBjFaATfB6UJjwupyyKQ4lL8Yyg7Lm
10 | 5N9ulBVD83txR9+eK2+L8CwjXSjUxDsIWbRiGDSuW5f1WN6ahydKyOLXs1BgwtnY
11 | VcxQacUfqEYqoMUTVGsCAwEAAaNNMEswHQYDVR0OBBYEFDR8ikVOYwwAWsjwb2sy
12 | 4MlY9v2MMB8GA1UdIwQYMBaAFDzhT/vohY7v5nuv6S+MFmRqeqBkMAkGA1UdEwQC
13 | MAAwDQYJKoZIhvcNAQELBQADggEBACcKXeoJFleZxJzt7x/HTAeNw5j9dM1FXGoT
14 | OKvhpk3XCpeG7TCyQeI7pakQ2nCucifKCHwsvkHGSu9IxQ787Csm5xx/5gp1n1Tb
15 | dZdORGe9LkMh3G0+Og+P679Y6oRDCW8upWVGjuIYzmwnm+lt2l/g2mNoN8B4l19H
16 | DwzADC+F73Vtvs7jkSdkAtIN+1dJOK8fA+kmaggyJLJWj3xmvXWzcyl8WI1oJ3qY
17 | cdOIVck1I5k7TruauL8St5yztkiefMnjuHLWaFDcgiwt77JyRQ+JLWlZBcPjnT7l
18 | qoy1mDsxaevoVdIokbiJB9w/rJs3ITDWwjUjFLMrgzzcTG2HI0A=
19 | -----END CERTIFICATE-----
20 |
--------------------------------------------------------------------------------
/tests/server/async_/conftest.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import pytest_asyncio
19 |
20 | from elastic_enterprise_search import AsyncAppSearch
21 |
22 |
23 | @pytest_asyncio.fixture
24 | async def app_search(ent_search_url, app_search_bearer_auth) -> AsyncAppSearch:
25 | async with AsyncAppSearch(
26 | ent_search_url, bearer_auth=app_search_bearer_auth
27 | ) as client:
28 | yield client
29 |
--------------------------------------------------------------------------------
/docs/guide/release-notes/8-18-0.asciidoc:
--------------------------------------------------------------------------------
1 | [[release-notes-8-18-0]]
2 | === 8.18.0 Release Notes
3 |
4 | Client is compatible with Elastic Enterprise Search 8.18.0
5 |
6 | [discrete]
7 | [WARNING]
8 | ====
9 | *App Search and Workplace Search will be discontinued in 9.0*
10 |
11 | Starting with Elastic version 9.0, the standalone Enterprise Search products, will no longer be included in our offering.
12 | They remain supported in their current form in version 8.x and will only receive security upgrades and fixes.
13 | Enterprise Search clients will continue to be supported in their current form throughout 8.x versions, according to our https://www.elastic.co/support/eol[EOL policy].
14 | We recommend transitioning to our actively developed https://www.elastic.co/elastic-stack[Elastic Stack] tools for your search use cases. However, if you're still using any Enterprise Search products, we recommend using the latest stable release of the clients.
15 |
16 | Here are some useful links with more information:
17 |
18 | * https://www.elastic.co/resources/enterprise-search/enterprise-search-faq[Enterprise Search FAQ]
19 | * https://www.elastic.co/guide/en/enterprise-search/current/upgrading-to-9-x.html[One stop shop for Upgrading to Elastic Search 9]
20 | ====
--------------------------------------------------------------------------------
/.buildkite/certs/ca.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDSjCCAjKgAwIBAgIVAOHbIwn3G13/zPk5SNvZHuLcYH+aMA0GCSqGSIb3DQEB
3 | CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu
4 | ZXJhdGVkIENBMB4XDTIzMTAxMDA1NTgzNFoXDTI2MTAwOTA1NTgzNFowNDEyMDAG
5 | A1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5lcmF0ZWQgQ0Ew
6 | ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCodaJxr1EsPHLZTqELNGye
7 | PoZY6hn818PMBtBlTwuCqo/BqpzVTg9TY43LDhG49BkrNNzjZPNb3689tWBZenAe
8 | G3D1mEhXiTRoelCHmmsb5meHCu/WERw+lexBbxKbwfQ6VIFx+2rE19IAq4xDxa1S
9 | I11uGhoctu6fj0Ic9uNXVl0u4xAlWvs9ieb5Av6Nd6YM0gWTsasvvkg5eySegSrC
10 | j12jdU9hwoZ0WHdROBxweiUdb5RMVpZ5f6hd6rxsXqqVsBdSHv024aQUhFOSDVCp
11 | xNmvV3ZHD6AXMHpwquudSZYg3lxdBc4gJTdiBh2azIQqQwiC0W7/jA+2EiAez+/v
12 | AgMBAAGjUzBRMB0GA1UdDgQWBBQ84U/76IWO7+Z7r+kvjBZkanqgZDAfBgNVHSME
13 | GDAWgBQ84U/76IWO7+Z7r+kvjBZkanqgZDAPBgNVHRMBAf8EBTADAQH/MA0GCSqG
14 | SIb3DQEBCwUAA4IBAQAyPA6zESiHEIDToszCwLGxwzBzDTaqR03hbReRNeH/rVXf
15 | gKkNVmpjPAmHXJnuzEvIZOuOhid+7ngbrY+bhszvNb7ksqnbadnsdPeSy9nLSQjs
16 | HcIaJXAdA3VpICfp1/rWGW5Dhs1Avptqhtgm5AjMThqz3zgKpf6Duh/t387eNGFK
17 | G4HtsdUNO934NvkVi8JD8iVc7E+UlkVEOSvfvGYznFyY5ySzCjqKESfcZjB5+wEs
18 | f1qC52MCwufx3rjONJRcNJA1DbD68HsAdb8U4QBSqOh8QnVxp+oiL89VAI1bGinR
19 | aV5mhIw+gWBHWnsEG/1kQKMAjpnpSlA3GfTQZWNx
20 | -----END CERTIFICATE-----
21 |
--------------------------------------------------------------------------------
/.buildkite/certs/testnode_no_san.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIDYjCCAkqgAwIBAgIVAIk+kKLNHf1uirTb0+lQ1BaYSMDTMA0GCSqGSIb3DQEB
3 | CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu
4 | ZXJhdGVkIENBMB4XDTIzMTAxMDA2MDE0N1oXDTI2MTAwOTA2MDE0N1owEzERMA8G
5 | A1UEAxMIaW5zdGFuY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDo
6 | Mmq+74NsEy4HSDtfeHOSKsRsIqzdLWUAGemP1+f7CEiZvRW6da5HSLsCup0xe4eK
7 | nNwF1ZblXHzfLo491hk13e549+AIU+UK12s28um6uP+YIoupii2Lx6GYhnnSf4ag
8 | oPyEXK3ZlOVDeWBPcdaJpY5bpdEaXhD90AiJFIQ5vJ9UkOWGfG6BLiBrz6jUjIcq
9 | lspA/liU8+aJtxjSZAbhNqxfrrt9rVbkjTCv6Xy9vv4/CNdnO17kdg46tU9c2hA8
10 | DFDC4HMElNMRzMfPhdKJhhtkmaXa2oU9e26Ws1QMzmkQeTF506fmc36CvsfuK+8B
11 | xCmAmIMQLIynUsf6rv9PAgMBAAGjgYswgYgwHQYDVR0OBBYEFD9QXcls3LTC4rmZ
12 | llEqRVlpQ7pqMB8GA1UdIwQYMBaAFDzhT/vohY7v5nuv6S+MFmRqeqBkMDsGA1Ud
13 | EQQ0MDKCCWxvY2FsaG9zdIIIaW5zdGFuY2WHBH8AAAGHEAAAAAAAAAAAAAAAAAAA
14 | AAGCA2VzMTAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQBBhdVTO9qY24f2
15 | ynPu2hiE5bHSYY4Nf4XvB3SW3yhFIj6IdDlFosbJjXv4xFYSgHFd3LpS3H8KS5Pg
16 | hE75D9DZgrXX4RHwouYrt4K9/z79hhvEMP1Knqc38q5bF5r67+LAjjrnvRuTKyTy
17 | 4QUEWaRsxBPruA9pot3J5y9+U5CyU5cO+U7BaWJscYgZmOxV/U4cDljh8vSy/t5X
18 | 8nReTLCFa5M3S86p5cqrp2GX52odvbUZFvSmBYc0TqteBXFdHMmeQ67Un8bYLXVd
19 | apO+3RZRdGz9XdoQ7uptSkrBX0ZYBo0T4/aUZAB6NNdPDag4F3D7DCG8FMQcUK6+
20 | mtFjwyxl
21 | -----END CERTIFICATE-----
22 |
--------------------------------------------------------------------------------
/tests/client/workplace_search/cassettes/test_index_documents_content_source_not_found.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '[{"id":1234,"title":"The Meaning of Time"}]'
4 | headers:
5 | authorization:
6 | - Bearer b6e5d30d7e5248533b5a8f5362e16853e2fc32826bc940aa32bf3ff1f1748f9b
7 | connection:
8 | - keep-alive
9 | content-type:
10 | - application/json
11 | method: POST
12 | uri: http://localhost:3002/api/ws/v1/sources/5f7e1407678c1d8435a949a8a/documents/bulk_create
13 | response:
14 | body:
15 | string: ''
16 | headers:
17 | Cache-Control:
18 | - no-cache
19 | Content-Security-Policy:
20 | - script-src 'nonce-2WatKGKyGZ0N2tLniJ96/A==' 'strict-dynamic' 'self'; object-src
21 | 'none'; base-uri 'none'; frame-ancestors 'self';
22 | Content-Type:
23 | - text/html
24 | Server:
25 | - Jetty(9.4.30.v20200611)
26 | Transfer-Encoding:
27 | - chunked
28 | X-Content-Type-Options:
29 | - nosniff
30 | X-Frame-Options:
31 | - SAMEORIGIN
32 | X-Request-Id:
33 | - dd000db7-3894-4427-9418-6bbfc5a2b466
34 | X-Runtime:
35 | - '0.023539'
36 | X-XSS-Protection:
37 | - 1; mode=block
38 | status:
39 | code: 404
40 | message: Not Found
41 | version: 1
42 |
--------------------------------------------------------------------------------
/catalog-info.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # yaml-language-server: $schema=https://json.schemastore.org/catalog-info.json
3 | apiVersion: backstage.io/v1alpha1
4 | kind: Component
5 | metadata:
6 | name: enterprise-search-python
7 | spec:
8 | type: library
9 | owner: group:devtools-team
10 | lifecycle: production
11 |
12 | ---
13 | # yaml-language-server: $schema=https://gist.githubusercontent.com/elasticmachine/988b80dae436cafea07d9a4a460a011d/raw/e57ee3bed7a6f73077a3f55a38e76e40ec87a7cf/rre.schema.json
14 | apiVersion: backstage.io/v1alpha1
15 | kind: Resource
16 | metadata:
17 | name: enterprise-search-python-test
18 | description: Test enterprise-search-python client
19 | spec:
20 | type: buildkite-pipeline
21 | owner: group:devtools-team
22 | system: buildkite
23 | implementation:
24 | apiVersion: buildkite.elastic.dev/v1
25 | kind: Pipeline
26 | metadata:
27 | name: enterprise-search-python-test
28 | spec:
29 | repository: elastic/enterprise-search-python
30 | pipeline_file: .buildkite/pipeline.yml
31 | teams:
32 | devtools-team:
33 | access_level: MANAGE_BUILD_AND_READ
34 | everyone:
35 | access_level: READ_ONLY
36 | provider_settings:
37 | build_pull_requests: true
38 | build_branches: true
39 | cancel_intermediate_builds: true
40 | cancel_intermediate_builds_branch_filter: '!main'
41 |
--------------------------------------------------------------------------------
/tests/server/test_httpbin.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import re
19 |
20 | from elastic_enterprise_search import EnterpriseSearch
21 |
22 |
23 | def test_httpbin():
24 | client = EnterpriseSearch("https://httpbin.org:443")
25 | resp = client.perform_request("GET", "/anything")
26 | assert resp.meta.status == 200
27 | assert re.match(
28 | r"^ent=8[.0-9]+p?,py=[.0-9]+p?,t=[.0-9]+p?,ur=[.0-9]+p?$",
29 | resp.body["headers"]["X-Elastic-Client-Meta"],
30 | )
31 | assert resp.body["headers"]["User-Agent"].startswith("enterprise-search-python/")
32 |
--------------------------------------------------------------------------------
/tests/server/conftest.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import pytest
19 |
20 | from elastic_enterprise_search import AppSearch, EnterpriseSearch
21 |
22 |
23 | @pytest.fixture(scope="session")
24 | def ent_search(ent_search_url, ent_search_basic_auth):
25 | with EnterpriseSearch(ent_search_url, basic_auth=ent_search_basic_auth) as client:
26 | yield client
27 |
28 |
29 | @pytest.fixture(scope="session")
30 | def app_search(ent_search_url, app_search_bearer_auth):
31 | with AppSearch(ent_search_url, bearer_auth=app_search_bearer_auth) as client:
32 | yield client
33 |
--------------------------------------------------------------------------------
/tests/client/enterprise_search/cassettes/test_get_version.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: null
4 | headers:
5 | accept:
6 | - application/json
7 | authorization:
8 | - Basic ZWxhc3RpYzp5cWNHcFJxVTlNazRGUW1zdkpLeEw5VW8=
9 | connection:
10 | - keep-alive
11 | method: GET
12 | uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/ent/v1/internal/version
13 | response:
14 | body:
15 | string: '{"number":"8.1.0","build_hash":"233d9108d258845ddd4a36915d45e22c19024981","build_date":"2022-03-03T14:31:36+00:00"}'
16 | headers:
17 | Cache-Control:
18 | - max-age=0, private, must-revalidate
19 | Content-Length:
20 | - '115'
21 | Content-Type:
22 | - application/json;charset=utf-8
23 | Date:
24 | - Thu, 10 Mar 2022 23:09:29 GMT
25 | Etag:
26 | - W/"ab82c4ff2aaae2cb69a653fb9329d3a8"
27 | Server:
28 | - Jetty(9.4.43.v20210629)
29 | Vary:
30 | - Accept-Encoding, User-Agent
31 | X-Cloud-Request-Id:
32 | - 2-JJQPlYQMWzxd0GA27qZw
33 | X-Found-Handling-Cluster:
34 | - efbb93a5d1bb4b3f90f192c495ee00d1
35 | X-Found-Handling-Instance:
36 | - instance-0000000000
37 | X-Request-Id:
38 | - 2-JJQPlYQMWzxd0GA27qZw
39 | X-Runtime:
40 | - '0.098871'
41 | status:
42 | code: 200
43 | message: OK
44 | version: 1
45 |
--------------------------------------------------------------------------------
/docs/guide/release-notes/7-11-0.asciidoc:
--------------------------------------------------------------------------------
1 | [[release-notes-7-11-0]]
2 | === 7.11.0 Release Notes
3 |
4 | [discrete]
5 | ==== General
6 |
7 | - Changed stability of the package to Generally Available, was Beta.
8 | - Updated APIs to the 7.11 specification
9 | - Documentation moved from the README to elastic.co/guide
10 | - Fixed encoding of arrays in query string to match Ruby on Rails
11 | - Changed `body` parameter to describe the content of the body for multiple APIs
12 | - Added the `X-Elastic-Client-Meta` HTTP header controlled by `meta_header` parameter
13 |
14 | [discrete]
15 | ==== App Search
16 |
17 | - Changed `body` parameter to `document_ids` for
18 | `delete_documents()` and `get_documents()` APIs
19 | - Changed `body` parameter to `documents` for
20 | `index_documents()` and `put_documents()` APIs
21 | - Changed `body` parameter to `source_engines` for
22 | `add_meta_engine_source()` and `delete_meta_engine_source()` APIs
23 | - Changed `queries` parameter to `body` for `multi_search()` API
24 | - Changed `body` parameter to `schema` for `put_schema()` API
25 | - Changed `synonyms` parameter to `body` for `create_synonym_set()`
26 | and `put_synonym_set()` APIs
27 |
28 | [discrete]
29 | ==== Workplace Search
30 |
31 | - Added `create_analytics_event()` API
32 | - Changed `content_source_key` parameter of all APIs to `content_source_id`
33 | - Changed `body` parameter to `documents` for `index_documents()` API
34 | - Changed `body` parameter to `document_ids` for `delete_documents()` API
35 |
--------------------------------------------------------------------------------
/tests/utils.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 |
19 | def pop_nested_json(from_, nested_key):
20 | """Utility function that pops a nested+dotted JSON key"""
21 | key_parts = nested_key.split(".")
22 | if len(key_parts) > 1:
23 | for i, key_part in enumerate(key_parts[:-1]):
24 | if key_part == "*":
25 | assert isinstance(from_, list)
26 | for item in from_:
27 | pop_nested_json(item, ".".join(key_parts[i + 1 :]))
28 | break
29 | else:
30 | from_ = from_[key_part]
31 | else:
32 | from_.pop(key_parts[-1])
33 | else:
34 | from_.pop(nested_key)
35 |
--------------------------------------------------------------------------------
/.buildkite/functions/wait-for-container.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #
3 | # Exposes a routine scripts can call to wait for a container if that container set up a health command
4 | #
5 | # Please source .buildkite/functions/imports.sh as a whole not just this file
6 | #
7 | # Version 1.0.1
8 | # - Initial version after refactor
9 | # - Make sure wait_for_contiainer is silent
10 |
11 | function wait_for_container {
12 | set +x
13 | until ! container_running "$1" || (container_running "$1" && [[ "$(docker inspect -f "{{.State.Health.Status}}" ${1})" != "starting" ]]); do
14 | echo ""
15 | docker inspect -f "{{range .State.Health.Log}}{{.Output}}{{end}}" ${1}
16 | echo -e "\033[34;1mINFO:\033[0m waiting for node $1 to be up\033[0m"
17 | sleep 2;
18 | done;
19 |
20 | # Always show logs if the container is running, this is very useful both on CI as well as while developing
21 | if container_running $1; then
22 | docker logs $1
23 | fi
24 |
25 | if ! container_running $1 || [[ "$(docker inspect -f "{{.State.Health.Status}}" ${1})" != "healthy" ]]; then
26 | cleanup_all_in_network $2
27 | echo
28 | echo -e "\033[31;1mERROR:\033[0m Failed to start $1 in detached mode beyond health checks\033[0m"
29 | echo -e "\033[31;1mERROR:\033[0m dumped the docker log before shutting the node down\033[0m"
30 | return 1
31 | else
32 | echo
33 | echo -e "\033[32;1mSUCCESS:\033[0m Detached and healthy: ${1} on docker network: ${network_name}\033[0m"
34 | return 0
35 | fi
36 | }
37 |
--------------------------------------------------------------------------------
/tests/client/app_search/cassettes/test_delete_documents.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '["park_yellowstone","park_zion"]'
4 | headers:
5 | accept:
6 | - application/json
7 | authorization:
8 | - Bearer private-ybzoyx7cok65hpxyxkwaarnn
9 | connection:
10 | - keep-alive
11 | content-type:
12 | - application/json
13 | method: DELETE
14 | uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/as/v1/engines/national-parks-demo/documents
15 | response:
16 | body:
17 | string: '[{"id":"park_yellowstone","deleted":true},{"id":"park_zion","deleted":true}]'
18 | headers:
19 | Cache-Control:
20 | - max-age=0, private, must-revalidate
21 | Content-Length:
22 | - '76'
23 | Content-Type:
24 | - application/json;charset=utf-8
25 | Date:
26 | - Thu, 10 Mar 2022 21:32:21 GMT
27 | Etag:
28 | - W/"7ef56ab768f6d5836354d95532c7d24f"
29 | Server:
30 | - Jetty(9.4.43.v20210629)
31 | Vary:
32 | - Origin
33 | - Accept-Encoding, User-Agent
34 | X-App-Search-Version:
35 | - 8.1.0
36 | X-Cloud-Request-Id:
37 | - wYT1lOu7Ty-5QnULISYhyA
38 | X-Found-Handling-Cluster:
39 | - efbb93a5d1bb4b3f90f192c495ee00d1
40 | X-Found-Handling-Instance:
41 | - instance-0000000000
42 | X-Request-Id:
43 | - wYT1lOu7Ty-5QnULISYhyA
44 | X-Runtime:
45 | - '0.073470'
46 | status:
47 | code: 200
48 | message: OK
49 | version: 1
50 |
--------------------------------------------------------------------------------
/elastic_enterprise_search/_serializer.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import datetime
19 |
20 | from elastic_transport import JsonSerializer as _JsonSerializer
21 |
22 | from ._utils import format_datetime
23 |
24 |
25 | class JsonSerializer(_JsonSerializer):
26 | """Same as elastic_transport.JsonSerializer except also formats
27 | datetime objects to RFC 3339. If a datetime is received without
28 | explicit timezone information then the timezone will be assumed
29 | to be the local timezone.
30 | """
31 |
32 | def default(self, data):
33 | if isinstance(data, datetime.datetime):
34 | return format_datetime(data)
35 | return super().default(data)
36 |
37 |
38 | JSONSerializer = JsonSerializer
39 |
--------------------------------------------------------------------------------
/docs/guide/release-notes/7-15-0.asciidoc:
--------------------------------------------------------------------------------
1 | [[release-notes-7-15-0]]
2 | === 7.15.0 Release Notes
3 |
4 | [discrete]
5 | ==== General
6 |
7 | - Updated APIs to the 7.15 specification
8 |
9 | [discrete]
10 | ==== App Search
11 |
12 | - Added the `delete_crawler_active_crawl_request`, `get_crawler_active_crawl_request`,
13 | `create_crawler_crawl_request`, `get_crawler_crawl_request`, `list_crawler_crawl_requests`,
14 | `create_crawler_crawl_rule`, `delete_crawler_crawl_rule`, `put_crawler_crawl_rule`,
15 | `delete_crawler_crawl_schedule`, `get_crawler_crawl_schedule`, `put_crawler_crawl_schedule`,
16 | `create_crawler_domain`, `delete_crawler_domain`, `get_crawler_domain`, `put_crawler_domain`,
17 | `get_crawler_domain_validation_result`, `create_crawler_entry_point`, `delete_crawler_entry_point`,
18 | `put_crawler_entry_point`, `get_crawler_metrics`, `get_crawler_overview`,
19 | `create_crawler_process_crawl`, `get_crawler_process_crawl_denied_urls`,
20 | `get_crawler_process_crawl`, `list_crawler_process_crawls`, `create_crawler_sitemap`,
21 | `delete_crawler_sitemap`, `put_crawler_sitemap`, `get_crawler_url_extraction_result`,
22 | `get_crawler_url_tracing_result`, `get_crawler_url_validation_result`,
23 | `get_crawler_user_agent` APIs
24 |
25 |
26 | [discrete]
27 | ==== Workplace Search
28 |
29 | - Added the `get_auto_query_refinement_details`, `delete_documents_by_query`,
30 | `get_triggers_blocklist`, `put_triggers_blocklist` APIs
31 | - Removed `delete_all_documents` API in favor of the `delete_documents_by_query`
32 | API without filters.
33 |
--------------------------------------------------------------------------------
/tests/client/workplace_search/cassettes/test_index_documents.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '[{"id":1234,"title":"The Meaning of Time","body":"Not much. It is a made
4 | up thing.","url":"https://example.com/meaning/of/time","created_at":"2019-06-01T12:00:00+00:00"},{"id":1235,"title":"The
5 | Meaning of Sleep","body":"Rest, recharge, and connect to the Ether.","url":"https://example.com/meaning/of/sleep","created_at":"2019-06-01T12:00:00+00:00"}]'
6 | headers:
7 | authorization:
8 | - Bearer b6e5d30d7e5248533b5a8f5362e16853e2fc32826bc940aa32bf3ff1f1748f9b
9 | connection:
10 | - keep-alive
11 | content-type:
12 | - application/json
13 | method: POST
14 | uri: http://localhost:3002/api/ws/v1/sources/5f7e1407678c1d8435a949a8/documents/bulk_create
15 | response:
16 | body:
17 | string: '{"results":[{"id":"1234","errors":[]},{"id":"1235","errors":[]}]}'
18 | headers:
19 | Cache-Control:
20 | - max-age=0, private, must-revalidate
21 | Content-Type:
22 | - application/json;charset=utf-8
23 | ETag:
24 | - W/"15cbaf9275e14d1103f38a1e9ee61bcf"
25 | Server:
26 | - Jetty(9.4.30.v20200611)
27 | Transfer-Encoding:
28 | - chunked
29 | X-Content-Type-Options:
30 | - nosniff
31 | X-Frame-Options:
32 | - SAMEORIGIN
33 | X-Request-Id:
34 | - aa3a2602-8657-4065-aca1-14cdbe2acb88
35 | X-Runtime:
36 | - '0.105895'
37 | X-XSS-Protection:
38 | - 1; mode=block
39 | status:
40 | code: 200
41 | message: OK
42 | version: 1
43 |
--------------------------------------------------------------------------------
/tests/client/workplace_search/cassettes/test_oauth_exchange_for_access_token_code.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: null
4 | headers:
5 | connection:
6 | - keep-alive
7 | content-type:
8 | - application/json
9 | method: POST
10 | uri: http://localhost:3002/ws/oauth/token?grant_type=authorization_code&client_id=1f87f8f6df473c06af79f88d1747afcb92e530295fad0fde340487673bec6ca6&client_secret=d26a2c9aaa5870e8d6bdf8169aaf21ce2d66ec2e0180ffc34a0390d254135311&redirect_uri=http:%2F%2Flocalhost:8000&code=c6424958616f102ce4e8b9e776ac8547aa06c1a603a27970547648080c43abb9
11 | response:
12 | body:
13 | string: '{"access_token":"00a456134c1964a0a9e82dff3ff93d8a20e9071a51f75b2bce18dde12908eb5d","token_type":"Bearer","expires_in":7200,"refresh_token":"57297c5f3a7fdf9dd63d03910c49c231d869e55e2e5934835c1ffa89c3c3b704","scope":"search"}'
14 | headers:
15 | Cache-Control:
16 | - no-store
17 | Content-Type:
18 | - application/json;charset=utf-8
19 | ETag:
20 | - W/"a66b964a39529e3642b1e250617f578b"
21 | Pragma:
22 | - no-cache
23 | Server:
24 | - Jetty(9.4.33.v20201020)
25 | Transfer-Encoding:
26 | - chunked
27 | Vary:
28 | - Accept-Encoding, User-Agent
29 | X-Content-Type-Options:
30 | - nosniff
31 | X-Frame-Options:
32 | - SAMEORIGIN
33 | X-Request-Id:
34 | - b27ceead-dba6-49f6-89a4-9411f75174cd
35 | X-Runtime:
36 | - '0.073148'
37 | X-XSS-Protection:
38 | - 1; mode=block
39 | status:
40 | code: 200
41 | message: OK
42 | version: 1
43 |
--------------------------------------------------------------------------------
/tests/client/workplace_search/cassettes/test_oauth_exchange_for_access_token_refresh_token.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: null
4 | headers:
5 | connection:
6 | - keep-alive
7 | content-type:
8 | - application/json
9 | method: POST
10 | uri: http://localhost:3002/ws/oauth/token?grant_type=refresh_token&client_id=1f87f8f6df473c06af79f88d1747afcb92e530295fad0fde340487673bec6ca6&client_secret=d26a2c9aaa5870e8d6bdf8169aaf21ce2d66ec2e0180ffc34a0390d254135311&redirect_uri=http:%2F%2Flocalhost:8000&refresh_token=57297c5f3a7fdf9dd63d03910c49c231d869e55e2e5934835c1ffa89c3c3b704
11 | response:
12 | body:
13 | string: '{"access_token":"494c72a1acaab2ac1dcf06882d874f5e54ce50c82d6b7183374597c2aceaddd6","token_type":"Bearer","expires_in":7200,"refresh_token":"43fb836d0600ce7a6e18087d4a674277493ec7be03b88756fe531b266db997f4","scope":"search"}'
14 | headers:
15 | Cache-Control:
16 | - no-store
17 | Content-Type:
18 | - application/json;charset=utf-8
19 | ETag:
20 | - W/"105b62ee6736d644f7bc8e55bb8a31d8"
21 | Pragma:
22 | - no-cache
23 | Server:
24 | - Jetty(9.4.33.v20201020)
25 | Transfer-Encoding:
26 | - chunked
27 | Vary:
28 | - Accept-Encoding, User-Agent
29 | X-Content-Type-Options:
30 | - nosniff
31 | X-Frame-Options:
32 | - SAMEORIGIN
33 | X-Request-Id:
34 | - 0f1e23b7-baf7-42ea-ae22-21b317fdb1a1
35 | X-Runtime:
36 | - '0.079740'
37 | X-XSS-Protection:
38 | - 1; mode=block
39 | status:
40 | code: 200
41 | message: OK
42 | version: 1
43 |
--------------------------------------------------------------------------------
/tests/client/app_search/cassettes/test_search_es_search.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"query":{"match":{"*":"client"}}}'
4 | headers:
5 | accept:
6 | - application/json
7 | authorization:
8 | - Bearer private-ybzoyx7cok65hpxyxkwaarnn
9 | connection:
10 | - keep-alive
11 | content-type:
12 | - application/json
13 | method: POST
14 | uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/as/v0/engines/elastic-docs/elasticsearch/_search?sort=_score
15 | response:
16 | body:
17 | string: '{"took":0,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":0,"relation":"eq"},"max_score":null,"hits":[]}}'
18 | headers:
19 | Cache-Control:
20 | - max-age=0, private, must-revalidate
21 | Content-Length:
22 | - '160'
23 | Content-Type:
24 | - application/json;charset=utf-8
25 | Date:
26 | - Mon, 08 Aug 2022 19:44:31 GMT
27 | Etag:
28 | - W/"95041d5366989a0ed1304624d63355eb"
29 | Server:
30 | - Jetty(9.4.43.v20210629)
31 | Vary:
32 | - Origin
33 | - Accept-Encoding, User-Agent
34 | X-App-Search-Version:
35 | - 8.4.0
36 | X-Cloud-Request-Id:
37 | - opbW3tOmRSOqGzamTfbLOQ
38 | X-Found-Handling-Cluster:
39 | - c99499ef963b4d11a9312e341f6d32cb
40 | X-Found-Handling-Instance:
41 | - instance-0000000001
42 | X-Request-Id:
43 | - opbW3tOmRSOqGzamTfbLOQ
44 | X-Runtime:
45 | - '0.084247'
46 | status:
47 | code: 200
48 | message: OK
49 | version: 1
50 |
--------------------------------------------------------------------------------
/docs/guide/release-notes/7-10-0.asciidoc:
--------------------------------------------------------------------------------
1 | [[release-notes-7-10-0]]
2 | === 7.10.0-beta1 Release Notes
3 |
4 | [discrete]
5 | ==== General
6 |
7 | - Updated APIs to the 7.10 specification
8 |
9 | [discrete]
10 | ==== App Search
11 |
12 | - Added `get_api_logs()`, `get_count_analytics()`, `create_curation()`,
13 | `delete_curation()`, `get_curation()`, `put_curation()`, `list_curations()`,
14 | `delete_documents()`, `get_documents()`, `index_documents()`, `list_documents()`,
15 | `put_documents()`, `create_engine()`, `delete_engine()`, `get_engine()`, `list_engines()`,
16 | `log_clickthrough()`, `add_meta_engine_source()`, `delete_meta_engine_source()`,
17 | `multi_search()`, `query_suggestion()`, `get_schema()`, `put_schema()`, `search`
18 | `get_search_settings()`, `put_search_settings()`, `reset_search_settings()`,
19 | `create_synonym_set()`, `delete_synonym_set()`, `get_synonym_set()`, `put_synonym_set()`,
20 | `list_synonym_sets()`, `get_top_clicks_analytics()`, and `get_top_queries_analytics` APIs
21 | - Added `create_signed_search_key()` method for creating Signed Search keys
22 |
23 | [discrete]
24 | ==== Workplace Search
25 |
26 | - Added `delete_documents()`, `index_documents()`, `list_external_identities()`,
27 | `create_external_identity()`, `delete_external_identity()`, `get_external_identity()`,
28 | `put_external_identity()`, `list_permissions()`, `add_user_permissions()`,
29 | `get_user_permissions()`, `put_user_permissions()`, `remove_user_permissions()`,
30 | and `search()` APIs
31 |
32 | [discrete]
33 | ==== Enterprise Search
34 |
35 | - Added `get_health()`, `get_read_only()`, `put_read_only()`,
36 | `get_stats()`, and `get_version()` APIs
37 |
--------------------------------------------------------------------------------
/tests/test_serializer.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import datetime
19 |
20 | from dateutil import tz
21 |
22 | from elastic_enterprise_search import JsonSerializer
23 |
24 |
25 | def test_serializer_formatting():
26 | serializer = JsonSerializer()
27 | assert (
28 | serializer.dumps(
29 | {
30 | "d": datetime.datetime(
31 | year=2020,
32 | month=12,
33 | day=11,
34 | hour=10,
35 | minute=9,
36 | second=8,
37 | tzinfo=tz.UTC,
38 | ),
39 | }
40 | )
41 | == b'{"d":"2020-12-11T10:09:08Z"}'
42 | )
43 | assert (
44 | serializer.dumps({"t": datetime.date(year=2020, month=1, day=29)})
45 | == b'{"t":"2020-01-29"}'
46 | )
47 |
--------------------------------------------------------------------------------
/docs/guide/release-notes/8-2-0.asciidoc:
--------------------------------------------------------------------------------
1 | [[release-notes-8-2-0]]
2 | === 8.2.0 Release Notes
3 |
4 | [discrete]
5 | ==== Added
6 |
7 | - Added `AsyncAppSearch`, `AsyncEnterpriseSearch`, and `AsyncWorkplaceSearch` clients which have async API methods.
8 | - Added the top-level `.options()` method to all client classes for modifying options per request.
9 | - Added parameters for JSON request body fields for all APIs
10 | - Added `basic_auth` parameter for specifying username and password authentication.
11 | - Added `bearer_auth` parameter for specifying authentication with HTTP Bearer tokens.
12 | - Added the `meta` property to `ApiError` and subclasses to access the HTTP response metadata of an error.
13 | - Added a check that a compatible version of `elastic-transport` package is installed.
14 |
15 | [discrete]
16 | ==== Changed
17 |
18 | - Changed responses to be objects with two properties, `meta` for response metadata (HTTP status, headers, node, etc) and `body` for the raw deserialized body object.
19 |
20 | [discrete]
21 | ==== Removed
22 |
23 | - Removed support for Python 2.7 and Python 3.5. The package now requires Python 3.6 or higher.
24 | - Removed the default URL of `http://localhost:3002`. The URL must now be specified explicitly, including scheme and port.
25 | - Removed the ability to use positional arguments with API methods. Going forward all API parameters must be specified as keyword parameters.
26 |
27 | [discrete]
28 | ==== Deprecated
29 |
30 | - Deprecated the `body` and `params` parameters for all API methods.
31 | - Deprecated setting transport options `http_auth`, `ignore`, `request_timeout`, and `headers` in API methods. All of these settings should be set via the `.options()` method instead.
32 |
--------------------------------------------------------------------------------
/.buildkite/certs/ca.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEowIBAAKCAQEAqHWica9RLDxy2U6hCzRsnj6GWOoZ/NfDzAbQZU8LgqqPwaqc
3 | 1U4PU2ONyw4RuPQZKzTc42TzW9+vPbVgWXpwHhtw9ZhIV4k0aHpQh5prG+Znhwrv
4 | 1hEcPpXsQW8Sm8H0OlSBcftqxNfSAKuMQ8WtUiNdbhoaHLbun49CHPbjV1ZdLuMQ
5 | JVr7PYnm+QL+jXemDNIFk7GrL75IOXsknoEqwo9do3VPYcKGdFh3UTgccHolHW+U
6 | TFaWeX+oXeq8bF6qlbAXUh79NuGkFIRTkg1QqcTZr1d2Rw+gFzB6cKrrnUmWIN5c
7 | XQXOICU3YgYdmsyEKkMIgtFu/4wPthIgHs/v7wIDAQABAoIBAACJFzIbUju8xB9d
8 | Y5+rKVPmHlE2tUxwzHuKjgEJxkntDDXxD+c8WfTJkpAnBEwSjT3uQMGBoaW/c/Qo
9 | mRzPtH7erCDrvKx35YXA1cld5qHuZ+fYU2OFJxIqhyy8vfy8Ghr8B8lP+PU/5mKq
10 | 05r84YyAS5y8/SuYMpv+kuw6pgWyDEIDY3BbWRsUzgmi6ghnZgSlxT6F/Xxylzaw
11 | DsBaCxwz4Nf5tdB0wusQA+tW1TzsbQf4UvtaZSkYrar0+wqbrd8g6ePn7fbWeWVt
12 | xD1Zg7GubbCZx222wBPITi6cxPtuZ0nLVIeEb3bDIjijIe7uZEKs1lQ0e6e2hTvy
13 | SQAH3AECgYEAuC0UU1WSo1/fp4ntIBwh80N5cDEVezuqmPjhBVRTvsUECXB3SJzw
14 | sHqsYIALLhFZbsNXefvfpHHvWv5mEK+/7m+7xVCREPTuyjRbzOfvonpEFzY3M2UC
15 | 90Oe6hiYSzsO4Wd1LQ3KlJ6Cpt3Ob6m3EGgUmeLtS1BMoNhoGlAaNe8CgYEA6ieE
16 | C1u3zOS88Xo0LRrJ6m9+ZlRlSgoZ1dJ+9WrJuyaEt+lnJHcbg3PQkNoU3+zaURqU
17 | RU8uol9qf8rbGTIb5IZ5as3BsSbJMaY0ZvuE2IsaUCitiYw5JyXWa5TAy43+BSwo
18 | etz/CVcfGmxvgM6wC6NXYxqXt08vhgyKI7rP5gECgYAza1qGXZjABg9SHh7W3SPZ
19 | X9gyq3F8406gwLNKIp3y39xdqkmTO0Wzb7xagMUeSne2hdERXHG23pxdwjLKq9ah
20 | Ag7harnliwxz5aRPk92CdjI2bMuCjMwELpvabZ1vO4DPC2xadMQ/M/X0Em8FG9Ph
21 | P72orQNlCHksWt7Nodl/fwKBgQDax8U2n6Hija6EqbvqkOcsZrRhhGWHglyVTrJV
22 | OEv404qaFDjM94T7k9DCJyHt/+4UbZMwF0XpbOGjObTxm8I4CfWUd1+M2EKQY0z/
23 | E+8SLRaO4xMSO7SDAXWQ21IwXyGDT7ka4zZgUci79alRXs1acmoKLSSooBI1W64O
24 | qFPsAQKBgEO8nOwgXRJUqNalmXBiyBSasSS7idJIJlCnQA1D5fXTyu7/I2j9ibLm
25 | YGo1oY0jzDpGXozy3iX9mUqiwHITqAypR+GQPT5qzlUlYv0p0q3RupRoOLZcEsom
26 | o+o5o8hU4cVP+PkwPT9cMRWdAKF2CdZUUmA1auhG5gzkbceSUzrO
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/tests/client/app_search/cassettes/test_list_engines.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: null
4 | headers:
5 | accept:
6 | - application/json
7 | authorization:
8 | - Bearer private-ybzoyx7cok65hpxyxkwaarnn
9 | connection:
10 | - keep-alive
11 | method: GET
12 | uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/as/v1/engines
13 | response:
14 | body:
15 | string: '{"meta":{"page":{"current":1,"total_pages":1,"total_results":3,"size":25}},"results":[{"name":"source-engine-2","type":"default","language":null,"index_create_settings_override":{},"document_count":0},{"name":"source-engine-1","type":"default","language":"en","index_create_settings_override":{},"document_count":0},{"name":"national-parks-demo","type":"default","language":null,"index_create_settings_override":{},"document_count":59}]}'
16 | headers:
17 | Cache-Control:
18 | - max-age=0, private, must-revalidate
19 | Content-Length:
20 | - '437'
21 | Content-Type:
22 | - application/json;charset=utf-8
23 | Date:
24 | - Thu, 10 Mar 2022 21:32:20 GMT
25 | Etag:
26 | - W/"2379e964efe468d67700b00bf2acabd4"
27 | Server:
28 | - Jetty(9.4.43.v20210629)
29 | Vary:
30 | - Origin
31 | - Accept-Encoding, User-Agent
32 | X-App-Search-Version:
33 | - 8.1.0
34 | X-Cloud-Request-Id:
35 | - oNrabsDjRnu-cplYaKSBvQ
36 | X-Found-Handling-Cluster:
37 | - efbb93a5d1bb4b3f90f192c495ee00d1
38 | X-Found-Handling-Instance:
39 | - instance-0000000000
40 | X-Request-Id:
41 | - oNrabsDjRnu-cplYaKSBvQ
42 | X-Runtime:
43 | - '0.076243'
44 | status:
45 | code: 200
46 | message: OK
47 | version: 1
48 |
--------------------------------------------------------------------------------
/.buildkite/certs/testnode.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEogIBAAKCAQEAyVRecgArGQAJLgpabX7E3ePL6fdarTfiM10+JYQ7AzaNfanq
3 | Aw5lvBrJswqcI/BxcR67O+K6NSSZjwZ5oEY4FK3x+7QvoTDcQx4UXewATcUFjIRZ
4 | L2T/UDQhL4+2V5M9FadPAoSdA97fpXf1oqNTEuBn/IKvIHQ9bjgszu/Nksxn1qAF
5 | CuZgcVRqdwnAHrkiu/3MRCPKfmOOPY4U9vY/WqWfqHnXE4D/TDkdRcGMVoBN8HpQ
6 | mPC6nLIpDiUvxjKDsubk326UFUPze3FH354rb4vwLCNdKNTEOwhZtGIYNK5bl/VY
7 | 3pqHJ0rI4tezUGDC2dhVzFBpxR+oRiqgxRNUawIDAQABAoIBAF/r7x2gn+gG4NjL
8 | PP9LNU/EvzxHSjAaXo77X2cvg5BJ1wrmwCRZoTYIi03fAbqLzfjH3Awxv2cfe3wt
9 | 6RfoLMMJhy/VzxWc+myN8cU38oMbGkQzMGzI0W3skF0hOw6pi6J79sRr24VjFCo5
10 | p9Inv6ZQPasMtpSfXT9cy1iC326PVIsvAKGaD+xpPBt20LzdJs/rAJV0CJ157QVT
11 | 3Rd7TVkPgp+unls5eDMlKzdrU3RxcVOD/RfpR57RxhX5aM9wxXa9Ke1a3rjBNESr
12 | vqym3vZ2hiwwudRY9rNsZg3RyArmWI3uDMPg7KBvakMCqyTQ+/ILcbEg5O8P5GKc
13 | XNhL2oECgYEAzZ1rgDUvZngDzm2SCeAFyMBzPNLNlosTRifPgZnzCpcvAMFnRrgx
14 | sTcIlcEQbfGiQQ7p+2ET74BTP2tNwmwVWgsc383awGDmbwZ5m1rMA3SKdpY/nopO
15 | XQ1PZG/CU3XtCmHhDEb915MGm9I9Q/pQwsoB1QDEtb6pfg37Q+6LKIECgYEA+qof
16 | CSYF/9P12ZEHjVo8nueARmnLADunhyjT7PntmZA+QuuBioIZZjTbq4KF4T74Q29/
17 | N5wF1wKqJQRl0+9sdx/wnSAhvMrTg0Tk9BYHDZ1n0GESBZ6J/EfpkoiWpxjhUjnC
18 | eHbJe8O8g8mALWX5oMKTCL23yX1hg0o+pCpSJusCgYAoy6IHpwXHk+pVa8H8+ZjM
19 | MvrqR30I8IEbe0ydjzj8kfB+euEN0//wBFZMuCiVV8r0k4vzF1jIPTLHM3gTKjS2
20 | T9wjv4k2gENYJfW80DAIQ3gxfTAUOabAqaJl8BKjUpN8at0m/XLh8cbu5bDIKwMZ
21 | EtF4PJXK5ZBldUq0OMEdgQKBgBrVq4znLS0+G8u24wAW8PZyAiGHodvchwrJLCbq
22 | eq096+xuGegiFWYDsqCh0INUom9VuGDTqyxhdKWR2vTdZNc77B5mGjaD4DDlZz1a
23 | PlcOytZcDfncBxmi+TZeuQIaf8S1ukP7M4a0ZbIWGErD5/111xfQd6Ryb8YGZL5e
24 | aX0RAoGAU7LT2ItP7tEjCmHzSkimFTp2wxMuziA8FygHBIgLQNu/k0GmEZYGagqT
25 | 7WPbu4Z2QHyc7XvPoA8zTbIQENmR3FhMPSGO8B6BjsgFy5S+eIGb4OsXLKdZQZcT
26 | 3xgs8YIwVitXn/r9iIk03BzH7+BmVTS+lwyUE5WmB12qodH4W3s=
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/.buildkite/certs/testnode_no_san.key:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIEogIBAAKCAQEA6DJqvu+DbBMuB0g7X3hzkirEbCKs3S1lABnpj9fn+whImb0V
3 | unWuR0i7ArqdMXuHipzcBdWW5Vx83y6OPdYZNd3uePfgCFPlCtdrNvLpurj/mCKL
4 | qYoti8ehmIZ50n+GoKD8hFyt2ZTlQ3lgT3HWiaWOW6XRGl4Q/dAIiRSEObyfVJDl
5 | hnxugS4ga8+o1IyHKpbKQP5YlPPmibcY0mQG4TasX667fa1W5I0wr+l8vb7+PwjX
6 | Zzte5HYOOrVPXNoQPAxQwuBzBJTTEczHz4XSiYYbZJml2tqFPXtulrNUDM5pEHkx
7 | edOn5nN+gr7H7ivvAcQpgJiDECyMp1LH+q7/TwIDAQABAoIBAHLPgxKX8X692ROG
8 | trzVHSgX93mUh67xZDBxn5gdZLoudV93LEg/KgZbQwTtaw5tiy6RswU7gFo2qhPc
9 | vD59H4gQDXtI7UCQ4v7CV2QbJlDKaq853Z7eEPk9o3x8eb4IinPgRhvYi6m7QsVj
10 | Pajqm+8BqmtMiSElg/dMJvxI5bx5w55ufSW/i36IbzPtKzRbfaEbQTnnzz/Do4/n
11 | sXYxyfQFSi/Ndpei+K9tveO/EkPt7AZbDA4gUne2ZUyAc6jiidUW/TLEeUxtiUFU
12 | M7e2KHZh3K9xHH/ZFcnXpndfcRShhq0+zZqXStEMgAePcI2ufp8fYMe70Vl71Ydy
13 | jgoC+UECgYEA8qalHD2w22TFQsJhgrCGObqCtMZKXJdcxfE/VuVChkS5eOEbroox
14 | xJWvffqMjRFK7Qm17v8IvSqfXfO2xv2R+tA9XSCCkWzd6K/QVeXuBVvUdhGnZM5d
15 | 4/WuQy83hiPkh5ldd62YUhjCYZV79kqxKXpCfj7voB5b7IuSLAAjOC8CgYEA9PiL
16 | MjPc040AT64OhrLN00yBbuwJUa93zRqaTSg0iEig5EvJuTXMgzU+La8jXF61Ngs7
17 | ZObpOl6oYOwnafxvXWKjTUUN6Kn3AMO6uXCc9XTWwnDppL9OlWxR2xOiMeBEJNSb
18 | 7ayPulIR5T02ntEcFGHlASkN6UcsVCXE2QaJwuECgYBKIp35deOt9CjMj8To//PS
19 | eWhrwNWBWoFuvJlkfCEKEr8z7lrdxb0U2cLHU6BTjT/+EeRzA5pw6S/NraNfQqOy
20 | JKNK657YvZFDAUw+okRJgNf1xskE5IQNHMfEIQ3uvtKYl0PWR8Rs+MGSvPAlvIZK
21 | LN9Z4PKnUf810yKyrMwV4wKBgD1mFi1NBmoXix5td8KXCjONl1tf2a4ZlqNXqZjx
22 | HMmTuo+91x+OtmWkcKMupGRAcJbNFePiZE527yjrx60u0hLL6DYzupq4DuqoJCLa
23 | cNysni858bWTJXUaIyIPt7VcinfYugRGHfgLHeUhBJGlw63wI1+5FH2Fkzy8AqyK
24 | kPjBAoGAP4fXbLM4sKXyOuieGi/2us8pUD+VX7piRnve59gaEPgby9MebkpgTAjP
25 | 6NBzGA/hzKH88ZBYH8lftLz0VAS9K7J3QQIVGrxaQSyN1zGLCGz3S0bDJl+ljsxR
26 | WtrKAjmQRoq1+rO4YpRuolWg1Qh1azGbQOvzlkto5Ybg+3s7E7w=
27 | -----END RSA PRIVATE KEY-----
28 |
--------------------------------------------------------------------------------
/tests/client/workplace_search/cassettes/test_oauth_exchange_for_access_token_invalid_grant.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: null
4 | headers:
5 | connection:
6 | - keep-alive
7 | content-type:
8 | - application/json
9 | method: POST
10 | uri: http://localhost:3002/ws/oauth/token?grant_type=authorization_code&client_id=1f87f8f6df473c06af79f88d1747afcb92e530295fad0fde340487673bec6ca6&client_secret=d26a2c9aaa5870e8d6bdf8169aaf21ce2d66ec2e0180ffc34a0390d254135311&redirect_uri=http:%2F%2Flocalhost:8000&code=7186fec34911a182606d2ab7fc36ea0ed4b8c32fef9929235cd80294422204ca
11 | response:
12 | body:
13 | string: '{"error":"invalid_grant","error_description":"The provided authorization
14 | grant is invalid, expired, revoked, does not match the redirection URI used
15 | in the authorization request, or was issued to another client."}'
16 | headers:
17 | Cache-Control:
18 | - no-store
19 | Content-Type:
20 | - application/json;charset=utf-8
21 | Pragma:
22 | - no-cache
23 | Server:
24 | - Jetty(9.4.33.v20201020)
25 | Transfer-Encoding:
26 | - chunked
27 | WWW-Authenticate:
28 | - Bearer realm="Workplace Search", error="invalid_grant", error_description="The
29 | provided authorization grant is invalid, expired, revoked, does not match
30 | the redirection URI used in the authorization request, or was issued to another
31 | client."
32 | X-Content-Type-Options:
33 | - nosniff
34 | X-Frame-Options:
35 | - SAMEORIGIN
36 | X-Request-Id:
37 | - a8afa4c2-a6ab-42d7-9bcb-b505b7850b31
38 | X-Runtime:
39 | - '0.015063'
40 | X-XSS-Protection:
41 | - 1; mode=block
42 | status:
43 | code: 401
44 | message: Unauthorized
45 | version: 1
46 |
--------------------------------------------------------------------------------
/tests/server/test_enterprise_search.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 |
19 | import pytest
20 |
21 | from elastic_enterprise_search import EnterpriseSearch
22 |
23 |
24 | def test_get_version(ent_search):
25 | assert set(ent_search.get_version()) == {"number", "build_hash", "build_date"}
26 |
27 |
28 | @pytest.mark.parametrize("include", [["app", "queues"], ("app", "queues")])
29 | def test_get_stats_include(ent_search: EnterpriseSearch, include):
30 | with pytest.raises(ValueError) as e:
31 | ent_search.get_stats(include="queues")
32 | assert str(e.value) == "'include' must be of type list or tuple"
33 |
34 | resp = ent_search.get_stats()
35 | assert resp.meta.status == 200
36 | assert set(resp.body.keys()) == {
37 | "app",
38 | "cluster_uuid",
39 | "connectors",
40 | "crawler",
41 | "http",
42 | "product_usage",
43 | "queues",
44 | }
45 |
46 | resp = ent_search.get_stats(include=include)
47 | assert resp.meta.status == 200
48 | assert set(resp.body.keys()) == {"app", "queues"}
49 |
--------------------------------------------------------------------------------
/tests/client/app_search/cassettes/test_query_suggestions.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"query":"ca","types":{"documents":{"fields":["title"]}}}'
4 | headers:
5 | accept:
6 | - application/json
7 | authorization:
8 | - Bearer private-ybzoyx7cok65hpxyxkwaarnn
9 | connection:
10 | - keep-alive
11 | content-type:
12 | - application/json
13 | method: POST
14 | uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/as/v1/engines/national-parks-demo/query_suggestion
15 | response:
16 | body:
17 | string: '{"results":{"documents":[{"suggestion":"cave"},{"suggestion":"canyon"},{"suggestion":"canyonlands"},{"suggestion":"capitol"},{"suggestion":"capitol
18 | reef"},{"suggestion":"carlsbad caverns"},{"suggestion":"cascades"},{"suggestion":"canyon
19 | of"},{"suggestion":"canyon of the"},{"suggestion":"canyon of the gunnison"}]},"meta":{"request_id":"H2-JTFaXT_qUjkzHdct-9g"}}'
20 | headers:
21 | Cache-Control:
22 | - max-age=0, private, must-revalidate
23 | Content-Length:
24 | - '362'
25 | Content-Type:
26 | - application/json;charset=utf-8
27 | Date:
28 | - Thu, 10 Mar 2022 21:32:23 GMT
29 | Etag:
30 | - W/"c4f24414760523290f3f43e33a602b0a"
31 | Server:
32 | - Jetty(9.4.43.v20210629)
33 | Vary:
34 | - Origin
35 | - Accept-Encoding, User-Agent
36 | X-App-Search-Version:
37 | - 8.1.0
38 | X-Cloud-Request-Id:
39 | - H2-JTFaXT_qUjkzHdct-9g
40 | X-Found-Handling-Cluster:
41 | - efbb93a5d1bb4b3f90f192c495ee00d1
42 | X-Found-Handling-Instance:
43 | - instance-0000000000
44 | X-Request-Id:
45 | - H2-JTFaXT_qUjkzHdct-9g
46 | X-Runtime:
47 | - '0.106425'
48 | X-St-Internal-Rails-Version:
49 | - 5.2.6
50 | status:
51 | code: 200
52 | message: OK
53 | version: 1
54 |
--------------------------------------------------------------------------------
/.buildkite/functions/imports.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #
3 | # Sets up all the common variables and imports relevant functions
4 | #
5 | # Version 1.0.1
6 | # - Initial version after refactor
7 | # - Validate STACK_VERSION asap
8 |
9 | function require_stack_version() {
10 | if [[ -z $STACK_VERSION ]]; then
11 | echo -e "\033[31;1mERROR:\033[0m Required environment variable [STACK_VERSION] not set\033[0m"
12 | exit 1
13 | fi
14 | }
15 |
16 | require_stack_version
17 |
18 | if [[ -z $es_node_name ]]; then
19 | # only set these once
20 | set -euo pipefail
21 | export TEST_SUITE=${TEST_SUITE-free}
22 | export RUNSCRIPTS=${RUNSCRIPTS-}
23 | export DETACH=${DETACH-false}
24 | export CLEANUP=${CLEANUP-false}
25 |
26 | export es_node_name=instance
27 | export elastic_password=changeme
28 | export elasticsearch_image=elasticsearch
29 | export elasticsearch_url=https://elastic:${elastic_password}@${es_node_name}:9200
30 | if [[ $TEST_SUITE != "platinum" ]]; then
31 | export elasticsearch_url=http://${es_node_name}:9200
32 | fi
33 | export external_elasticsearch_url=${elasticsearch_url/$es_node_name/localhost}
34 | export elasticsearch_container="${elasticsearch_image}:${STACK_VERSION}"
35 |
36 | export suffix=rest-test
37 | export moniker=$(echo "$elasticsearch_container" | tr -C "[:alnum:]" '-')
38 | export network_name=${moniker}${suffix}
39 |
40 | export ssl_cert="${script_path}/certs/testnode.crt"
41 | export ssl_key="${script_path}/certs/testnode.key"
42 | export ssl_ca="${script_path}/certs/ca.crt"
43 |
44 | fi
45 |
46 | export script_path=$(dirname $(realpath -s $0))
47 | source $script_path/functions/cleanup.sh
48 | source $script_path/functions/wait-for-container.sh
49 | trap "cleanup_trap ${network_name}" EXIT
50 |
51 |
52 | if [[ "$CLEANUP" == "true" ]]; then
53 | cleanup_all_in_network $network_name
54 | exit 0
55 | fi
56 |
57 | echo -e "\033[34;1mINFO:\033[0m Creating network $network_name if it does not exist already \033[0m"
58 | docker network inspect "$network_name" > /dev/null 2>&1 || docker network create "$network_name"
59 |
60 |
--------------------------------------------------------------------------------
/.buildkite/certs/README.md:
--------------------------------------------------------------------------------
1 | # CI certificates
2 |
3 | This directory contains certificates that can be used to test against Elasticsearch in CI. Perhaps the easiest way to generate certificates is using
4 | [`elasticsearch-certutil`](https://www.elastic.co/guide/en/elasticsearch/reference/current/certutil.html)
5 |
6 | ## Generate new Certificate Authority cert and key
7 |
8 | docker run \
9 | -v "$PWD:/var/tmp" \
10 | --rm docker.elastic.co/elasticsearch/elasticsearch:8.10.2 \
11 | ./bin/elasticsearch-certutil ca \
12 | --pass "" --pem \
13 | --out /var/tmp/bundle.zip
14 | ```
15 |
16 |
17 | ## Generating new certificates using the Certificate Authority cert and key
18 |
19 | The `ca.crt` and `ca.key` can be used to generate any other certificates that may be needed
20 | for CI.
21 | Using the elasticsearch docker container, run the following from the `.buildkite/certs` directory
22 |
23 | ```sh
24 | docker run \
25 | -v "$PWD:/var/tmp" \
26 | --rm docker.elastic.co/elasticsearch/elasticsearch:8.10.2 \
27 | ./bin/elasticsearch-certutil cert \
28 | --ca-cert /var/tmp/ca.crt --ca-key /var/tmp/ca.key --pem \
29 | --out /var/tmp/bundle.zip
30 | ```
31 |
32 | This will output a `bundle.zip` file containing a directory named `instance` containing
33 | `instance.crt` and `instance.key` in PEM format.
34 |
35 | The CN Subject name can be changed using
36 |
37 | ```sh
38 | docker run \
39 | -v "$PWD:/var/tmp" \
40 | --rm docker.elastic.co/elasticsearch/elasticsearch:8.10.2 \
41 | ./bin/elasticsearch-certutil cert \
42 | --ca-cert /var/tmp/ca.crt --ca-key /var/tmp/ca.key --pem \
43 | --out /var/tmp/bundle.zip \
44 | --name foo
45 | ```
46 |
47 | The directory in `bundle.zip` will now be named `foo` and contain
48 | `foo.crt` and `foo.key` in PEM format.
49 |
50 | Additional DNS and IP SAN entries can be added with `--dns` and `--ip`, respectively.
51 |
52 | ```sh
53 | docker run \
54 | -v "$PWD:/var/tmp" \
55 | --rm docker.elastic.co/elasticsearch/elasticsearch:8.10.2 \
56 | ./bin/elasticsearch-certutil cert \
57 | --ca-cert /var/tmp/ca.crt --ca-key /var/tmp/ca.key --pem \
58 | --out /var/tmp/bundle.zip \
59 | --dns instance --dns localhost --dns es1 --ip 127.0.0.1 --ip ::1
60 | ```
61 |
--------------------------------------------------------------------------------
/.buildkite/functions/cleanup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #
3 | # Shared cleanup routines between different steps
4 | #
5 | # Please source .buildkite/functions/imports.sh as a whole not just this file
6 | #
7 | # Version 1.0.0
8 | # - Initial version after refactor
9 |
10 | function cleanup_volume {
11 | if [[ "$(docker volume ls -q -f name=$1)" ]]; then
12 | echo -e "\033[34;1mINFO:\033[0m Removing volume $1\033[0m"
13 | (docker volume rm "$1") || true
14 | fi
15 | }
16 | function container_running {
17 | if [[ "$(docker ps -q -f name=$1)" ]]; then
18 | return 0;
19 | else return 1;
20 | fi
21 | }
22 | function cleanup_node {
23 | if container_running "$1"; then
24 | echo -e "\033[34;1mINFO:\033[0m Removing container $1\033[0m"
25 | (docker container rm --force --volumes "$1") || true
26 | fi
27 | if [[ -n "$1" ]]; then
28 | echo -e "\033[34;1mINFO:\033[0m Removing volume $1-${suffix}-data\033[0m"
29 | cleanup_volume "$1-${suffix}-data"
30 | fi
31 | }
32 | function cleanup_network {
33 | if [[ "$(docker network ls -q -f name=$1)" ]]; then
34 | echo -e "\033[34;1mINFO:\033[0m Removing network $1\033[0m"
35 | (docker network rm "$1") || true
36 | fi
37 | }
38 |
39 | function cleanup_trap {
40 | status=$?
41 | set +x
42 | if [[ "$DETACH" != "true" ]]; then
43 | echo -e "\033[34;1mINFO:\033[0m clean the network if not detached (start and exit)\033[0m"
44 | cleanup_all_in_network "$1"
45 | fi
46 | # status is 0 or SIGINT
47 | if [[ "$status" == "0" || "$status" == "130" ]]; then
48 | echo -e "\n\033[32;1mSUCCESS run-tests\033[0m"
49 | exit 0
50 | else
51 | echo -e "\n\033[31;1mFAILURE during run-tests\033[0m"
52 | exit ${status}
53 | fi
54 | };
55 | function cleanup_all_in_network {
56 |
57 | if [[ -z "$(docker network ls -q -f name="^$1\$")" ]]; then
58 | echo -e "\033[34;1mINFO:\033[0m $1 is already deleted\033[0m"
59 | return 0
60 | fi
61 | containers=$(docker network inspect -f '{{ range $key, $value := .Containers }}{{ printf "%s\n" .Name}}{{ end }}' $1)
62 | while read -r container; do
63 | cleanup_node "$container"
64 | done <<< "$containers"
65 | cleanup_network $1
66 | echo -e "\033[32;1mSUCCESS:\033[0m Cleaned up and exiting\033[0m"
67 | };
68 |
--------------------------------------------------------------------------------
/utils/run-unasync.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import os
19 | from pathlib import Path
20 |
21 | import unasync
22 |
23 |
24 | def main():
25 | # Unasync all the generated async code
26 | additional_replacements = {
27 | # We want to rewrite to 'Transport' instead of 'SyncTransport', etc
28 | "AsyncTransport": "Transport",
29 | "AsyncAppSearch": "AppSearch",
30 | "AsyncEnterpriseSearch": "EnterpriseSearch",
31 | "AsyncWorkplaceSearch": "WorkplaceSearch",
32 | "_AsyncAppSearch": "_AppSearch",
33 | "_AsyncEnterpriseSearch": "_EnterpriseSearch",
34 | "_AsyncWorkplaceSearch": "_WorkplaceSearch",
35 | }
36 | rules = [
37 | unasync.Rule(
38 | fromdir="/elastic_enterprise_search/_async/client/",
39 | todir="/elastic_enterprise_search/_sync/client/",
40 | additional_replacements=additional_replacements,
41 | ),
42 | ]
43 |
44 | filepaths = []
45 | for root, _, filenames in os.walk(
46 | Path(__file__).absolute().parent.parent / "elastic_enterprise_search/_async"
47 | ):
48 | for filename in filenames:
49 | if filename.rpartition(".")[-1] in (
50 | "py",
51 | "pyi",
52 | ):
53 | filepaths.append(os.path.join(root, filename))
54 |
55 | unasync.unasync_files(filepaths, rules)
56 |
57 |
58 | if __name__ == "__main__":
59 | main()
60 |
--------------------------------------------------------------------------------
/elastic_enterprise_search/exceptions.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import typing as t
19 | import warnings
20 |
21 | from elastic_transport import ApiError as _ApiError
22 |
23 |
24 | class ApiError(_ApiError):
25 | @property
26 | def status(self) -> int:
27 | warnings.warn(
28 | "ApiError.status is deprecated in favor of ApiError.meta.status",
29 | category=DeprecationWarning,
30 | stacklevel=2,
31 | )
32 | return self.meta.status
33 |
34 |
35 | class BadGatewayError(ApiError):
36 | pass
37 |
38 |
39 | class BadRequestError(ApiError):
40 | pass
41 |
42 |
43 | class ConflictError(ApiError):
44 | pass
45 |
46 |
47 | class ForbiddenError(ApiError):
48 | pass
49 |
50 |
51 | class GatewayTimeoutError(ApiError):
52 | pass
53 |
54 |
55 | class InternalServerError(ApiError):
56 | pass
57 |
58 |
59 | class NotFoundError(ApiError):
60 | pass
61 |
62 |
63 | class PayloadTooLargeError(ApiError):
64 | pass
65 |
66 |
67 | class ServiceUnavailableError(ApiError):
68 | pass
69 |
70 |
71 | class UnauthorizedError(ApiError):
72 | pass
73 |
74 |
75 | _HTTP_EXCEPTIONS: t.Dict[int, ApiError] = {
76 | 400: BadRequestError,
77 | 401: UnauthorizedError,
78 | 403: ForbiddenError,
79 | 404: NotFoundError,
80 | 409: ConflictError,
81 | 413: PayloadTooLargeError,
82 | 500: InternalServerError,
83 | 502: BadGatewayError,
84 | 503: ServiceUnavailableError,
85 | 504: GatewayTimeoutError,
86 | }
87 |
--------------------------------------------------------------------------------
/tests/server/async_/test_app_search.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import pytest
19 |
20 | from elastic_enterprise_search import AsyncAppSearch, NotFoundError
21 |
22 |
23 | @pytest.mark.asyncio
24 | async def test_engines(app_search: AsyncAppSearch):
25 | await app_search.options(ignore_status=404).delete_engine(
26 | engine_name="example-engine"
27 | )
28 |
29 | resp = await app_search.create_engine(engine_name="example-engine")
30 | assert resp.meta.status == 200
31 | assert resp.body == {
32 | "document_count": 0,
33 | "index_create_settings_override": {},
34 | "language": None,
35 | "name": "example-engine",
36 | "type": "default",
37 | }
38 |
39 | resp = await app_search.list_engines()
40 | assert resp.meta.status == 200
41 | assert {
42 | "name": "example-engine",
43 | "type": "default",
44 | "language": None,
45 | "index_create_settings_override": {},
46 | "document_count": 0,
47 | } in resp.body["results"]
48 |
49 | resp = await app_search.delete_engine(engine_name="example-engine")
50 | assert resp.meta.status == 200
51 | assert resp.body == {"deleted": True}
52 |
53 |
54 | @pytest.mark.asyncio
55 | async def test_error(app_search: AsyncAppSearch):
56 | with pytest.raises(NotFoundError) as e:
57 | await app_search.get_engine(engine_name="does-not-exist")
58 |
59 | assert e.value.meta.status == 404
60 | assert e.value.message == "{'errors': ['Could not find engine.']}"
61 | assert e.value.body == {"errors": ["Could not find engine."]}
62 |
--------------------------------------------------------------------------------
/tests/client/app_search/cassettes/test_not_authorized.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: null
4 | headers:
5 | accept:
6 | - application/json
7 | authorization:
8 | - ''
9 | connection:
10 | - keep-alive
11 | method: GET
12 | uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/as/v1/engines
13 | response:
14 | body:
15 | string: '{"error":"You need to sign in before continuing."}'
16 | headers:
17 | Cache-Control:
18 | - no-cache
19 | Content-Length:
20 | - '50'
21 | Content-Type:
22 | - application/json;charset=utf-8
23 | Date:
24 | - Thu, 10 Mar 2022 23:02:05 GMT
25 | Server:
26 | - Jetty(9.4.43.v20210629)
27 | Vary:
28 | - Origin
29 | Www-Authenticate:
30 | - Basic realm="Swiftype"
31 | X-Cloud-Request-Id:
32 | - bE0Ue1t5Sumz_SCvBfcFcg
33 | X-Found-Handling-Cluster:
34 | - efbb93a5d1bb4b3f90f192c495ee00d1
35 | X-Found-Handling-Instance:
36 | - instance-0000000000
37 | X-Request-Id:
38 | - bE0Ue1t5Sumz_SCvBfcFcg
39 | X-Runtime:
40 | - '0.024420'
41 | status:
42 | code: 401
43 | message: Unauthorized
44 | - request:
45 | body: null
46 | headers:
47 | accept:
48 | - application/json
49 | authorization:
50 | - ''
51 | connection:
52 | - keep-alive
53 | method: GET
54 | uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/as/v1/engines
55 | response:
56 | body:
57 | string: '{"error":"You need to sign in before continuing."}'
58 | headers:
59 | Cache-Control:
60 | - no-cache
61 | Content-Length:
62 | - '50'
63 | Content-Type:
64 | - application/json;charset=utf-8
65 | Date:
66 | - Thu, 10 Mar 2022 23:02:05 GMT
67 | Server:
68 | - Jetty(9.4.43.v20210629)
69 | Vary:
70 | - Origin
71 | Www-Authenticate:
72 | - Basic realm="Swiftype"
73 | X-Cloud-Request-Id:
74 | - H3-Hjss2RzKiWyOuv33RvQ
75 | X-Found-Handling-Cluster:
76 | - efbb93a5d1bb4b3f90f192c495ee00d1
77 | X-Found-Handling-Instance:
78 | - instance-0000000000
79 | X-Request-Id:
80 | - H3-Hjss2RzKiWyOuv33RvQ
81 | X-Runtime:
82 | - '0.019833'
83 | status:
84 | code: 401
85 | message: Unauthorized
86 | version: 1
87 |
--------------------------------------------------------------------------------
/.buildkite/run-repository.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #
3 | # Called by entry point `run-test` use this script to add your repository specific test commands
4 | #
5 | # Once called Elasticsearch is up and running
6 | #
7 | # Its recommended to call `imports.sh` as defined here so that you get access to all variables defined there
8 | #
9 | # Any parameters that test-matrix.yml defines should be declared here with appropiate defaults
10 |
11 | script_path=$(dirname $(realpath -s $0))
12 | source $script_path/functions/imports.sh
13 | set -euo pipefail
14 |
15 | echo -e "\033[34;1mINFO:\033[0m VERSION: ${STACK_VERSION}\033[0m"
16 | echo -e "\033[34;1mINFO:\033[0m TEST_SUITE: ${TEST_SUITE}\033[0m"
17 | echo -e "\033[34;1mINFO:\033[0m RUNSCRIPTS: ${RUNSCRIPTS}\033[0m"
18 | echo -e "\033[34;1mINFO:\033[0m URL: ${elasticsearch_url}\033[0m"
19 |
20 | echo -e "\033[34;1mINFO:\033[0m pinging Elasticsearch ..\033[0m"
21 | curl --insecure --fail $external_elasticsearch_url/_cluster/health?pretty
22 |
23 | # Unquote this block for 7.x branches to enable compatibility mode.
24 | # See: knowledgebase/jenkins-es-compatibility-mode.md
25 | # if [[ "$STACK_VERSION" == "8.0.0-SNAPSHOT" && -z "${ELASTIC_CLIENT_APIVERSIONING+x}" ]]; then
26 | # export STACK_VERSION="7.x-SNAPSHOT"
27 | # export ELASTIC_CLIENT_APIVERSIONING="true"
28 | # fi
29 |
30 | if [[ "$RUNSCRIPTS" = *"enterprise-search"* ]]; then
31 | enterprise_search_url="http://localhost:3002"
32 | echo -e "\033[34;1mINFO:\033[0m pinging Enterprise Search ..\033[0m"
33 | curl -I --fail $enterprise_search_url
34 | fi
35 |
36 | echo -e "\033[32;1mSUCCESS:\033[0m successfully started the ${STACK_VERSION} stack.\033[0m"
37 |
38 | echo -e "\033[34;1mINFO:\033[0m STACK_VERSION ${STACK_VERSION}\033[0m"
39 | echo -e "\033[34;1mINFO:\033[0m PYTHON_VERSION ${PYTHON_VERSION}\033[0m"
40 |
41 | echo ":docker: :python: :elastic-enterprise-search: Build elastic/enterprise-search-python image"
42 |
43 | docker build \
44 | --file .buildkite/Dockerfile \
45 | --tag elastic/enterprise-search-python \
46 | --build-arg PYTHON_VERSION="$PYTHON_VERSION" \
47 | .
48 |
49 | echo ":docker: :python: :elastic-enterprise-search: Run elastic/enterprise-search-python container"
50 |
51 | mkdir -p "$(pwd)/junit"
52 | docker run \
53 | --network ${network_name} \
54 | --name enterprise-search-python \
55 | --rm \
56 | -e ENTERPRISE_SEARCH_PASSWORD="$elastic_password" \
57 | -v "$(pwd)/junit:/junit" \
58 | elastic/enterprise-search-python \
59 | bash -c "set -euo pipefail; nox -s test-$PYTHON_VERSION; [ -f ./junit/${BUILDKITE_JOB_ID:-}-junit.xml ] && mv ./junit/${BUILDKITE_JOB_ID:-}-junit.xml /junit || echo 'No JUnit artifact found'"
60 |
--------------------------------------------------------------------------------
/tests/client/test_options.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import inspect
19 |
20 | import pytest
21 |
22 | from elastic_enterprise_search import AppSearch, EnterpriseSearch, WorkplaceSearch
23 | from tests.conftest import DummyNode
24 |
25 |
26 | @pytest.mark.parametrize("request_timeout", [3, 5.0])
27 | def test_request_timeout(request_timeout):
28 | client = EnterpriseSearch(node_class=DummyNode, meta_header=False)
29 | client.get_version(request_timeout=request_timeout)
30 |
31 | calls = client.transport.node_pool.get().calls
32 | assert calls == [
33 | (
34 | ("GET", "/api/ent/v1/internal/version"),
35 | {
36 | "body": None,
37 | "headers": {"accept": "application/json"},
38 | "request_timeout": request_timeout,
39 | },
40 | )
41 | ]
42 |
43 |
44 | @pytest.mark.parametrize("client_cls", [EnterpriseSearch, AppSearch, WorkplaceSearch])
45 | def test_client_class_init_parameters(client_cls):
46 | # Ensures that all client signatures are identical.
47 | sig = inspect.signature(client_cls)
48 | assert set(sig.parameters) == {
49 | "_transport",
50 | "basic_auth",
51 | "bearer_auth",
52 | "ca_certs",
53 | "client_cert",
54 | "client_key",
55 | "connections_per_node",
56 | "dead_node_backoff_factor",
57 | "headers",
58 | "hosts",
59 | "http_auth",
60 | "http_compress",
61 | "max_dead_node_backoff",
62 | "max_retries",
63 | "meta_header",
64 | "node_class",
65 | "request_timeout",
66 | "retry_on_status",
67 | "retry_on_timeout",
68 | "ssl_assert_fingerprint",
69 | "ssl_assert_hostname",
70 | "ssl_context",
71 | "ssl_show_warn",
72 | "ssl_version",
73 | "transport_class",
74 | "verify_certs",
75 | }
76 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to enterprise-search-python
2 |
3 | ## Contributing Code Changes
4 |
5 | 1. Please make sure you have signed the [Contributor License
6 | Agreement](http://www.elastic.co/contributor-agreement/). We are not
7 | asking you to assign copyright to us, but to give us the right to distribute
8 | your code without restriction. We ask this of all contributors in order to
9 | assure our users of the origin and continuing existence of the code. You only
10 | need to sign the CLA once.
11 |
12 | 2. Run the linter and test suite to ensure your changes do not break existing code:
13 |
14 | Install [`nox`](https://nox.thea.codes) for task management:
15 |
16 | ```
17 | $ python -m pip install nox
18 | ```
19 |
20 | Auto-format and lint your changes:
21 |
22 | ```
23 | $ nox -rs format
24 | ```
25 |
26 | Run the test suite:
27 |
28 | ```
29 | # Runs against Python 2.7 and 3.6
30 | $ nox -rs test-2.7 test-3.6
31 |
32 | # Runs against all available Python versions
33 | $ nox -rs test
34 | ```
35 |
36 | 3. Rebase your changes. Update your local repository with the most recent code
37 | from the main `enterprise-search-python` repository and rebase your branch
38 | on top of the latest `main` branch. All of your changes will be squashed
39 | into a single commit so don't worry about pushing multiple times.
40 |
41 | 4. Submit a pull request. Push your local changes to your forked repository
42 | and [submit a pull request](https://github.com/elastic/enterprise-search-python/pulls)
43 | and mention the issue number if any (`Closes #123`) Make sure that you
44 | add or modify tests related to your changes so that CI will pass.
45 |
46 | 5. Sit back and wait. There may be some discussion on your pull request and
47 | if changes are needed we would love to work with you to get your pull request
48 | merged into enterprise-search-python.
49 |
50 | ## Running Integration Tests
51 |
52 | Run the full integration test suite with `$ .buildkite/run-tests`.
53 |
54 | There are several environment variabels that control integration tests:
55 |
56 | - `PYTHON_VERSION`: Version of Python to use, defaults to `3.9`
57 | - `STACK_VERSION`: Version of Elasticsearch to use. These should be
58 | the same as tags of `docker.elastic.co/elasticsearch/elasticsearch`
59 | such as `8.0.0-SNAPSHOT`, `7.11-SNAPSHOT`, etc. Defaults to the
60 | same `*-SNAPSHOT` version as the branch.
61 | - `ENTERPRISE_SEARCH_URL`: URL for the Enterprise Search instance
62 | - `ENTERPRISE_SEARCH_PASSWORD`: Password for the `elastic` user on Enterprise Search. This is typically the same as the `elastic` password on Elasticsearch.
63 | - `APP_SEARCH_PRIVATE_KEY`: Private key for App Search
64 |
--------------------------------------------------------------------------------
/tests/client/test_enterprise_search.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | from elastic_enterprise_search import AppSearch, EnterpriseSearch, WorkplaceSearch
19 | from tests.conftest import DummyNode
20 |
21 |
22 | def test_sub_clients():
23 | client = EnterpriseSearch()
24 | assert isinstance(client.app_search, AppSearch)
25 | assert isinstance(client.workplace_search, WorkplaceSearch)
26 |
27 | # Requests Session is shared for pooling
28 | assert client.transport is client.app_search.transport
29 | assert client.transport is client.workplace_search.transport
30 |
31 |
32 | def test_sub_client_auth():
33 | client = EnterpriseSearch(node_class=DummyNode, meta_header=False)
34 |
35 | # Using options on individual clients
36 | client.options(bearer_auth="enterprise-search").perform_request(
37 | "GET", "/enterprise-search"
38 | )
39 | client.app_search.options(bearer_auth="app-search").perform_request(
40 | "GET", "/app-search"
41 | )
42 | client.workplace_search.options(bearer_auth="workplace-search").perform_request(
43 | "GET", "/workplace-search"
44 | )
45 |
46 | # Authenticating doesn't modify other clients
47 | client.options(bearer_auth="not-app-search").app_search.perform_request(
48 | "GET", "/not-app-search"
49 | )
50 | client.options(bearer_auth="not-workplace-search").workplace_search.perform_request(
51 | "GET", "/not-workplace-search"
52 | )
53 |
54 | # The Authorziation header gets hidden
55 | calls = client.transport.node_pool.get().calls
56 | headers = [
57 | (target, kwargs["headers"].get("Authorization", None))
58 | for ((_, target), kwargs) in calls
59 | ]
60 |
61 | assert headers == [
62 | ("/enterprise-search", "Bearer enterprise-search"),
63 | ("/app-search", "Bearer app-search"),
64 | ("/workplace-search", "Bearer workplace-search"),
65 | ("/not-app-search", None),
66 | ("/not-workplace-search", None),
67 | ]
68 |
--------------------------------------------------------------------------------
/tests/client/test_client_meta.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import re
19 |
20 | import pytest
21 |
22 | from tests.conftest import DummyNode
23 |
24 |
25 | def test_client_meta_header_http_meta(client_class):
26 | # Test with HTTP connection meta
27 | class DummyNodeWithMeta(DummyNode):
28 | _CLIENT_META_HTTP_CLIENT = ("dm", "1.2.3")
29 |
30 | client = client_class(node_class=DummyNodeWithMeta)
31 | client.perform_request("GET", "/")
32 |
33 | calls = client.transport.node_pool.get().calls
34 | assert len(calls) == 1
35 | headers = calls[0][1]["headers"]
36 | assert re.match(
37 | r"^ent=[0-9.]+p?,py=[0-9.]+p?,t=[0-9.]+p?,dm=[0-9.]+p?$",
38 | headers["x-elastic-client-meta"],
39 | )
40 |
41 |
42 | def test_client_meta_header_no_http_meta(client_class):
43 | # Test without an HTTP connection meta
44 | client = client_class(node_class=DummyNode)
45 | client.perform_request("GET", "/")
46 |
47 | calls = client.transport.node_pool.get().calls
48 | assert len(calls) == 1
49 | headers = calls[0][1]["headers"]
50 | assert re.match(
51 | r"^ent=[0-9.]+p?,py=[0-9.]+p?,t=[0-9.]+p?$", headers["x-elastic-client-meta"]
52 | )
53 |
54 |
55 | def test_client_meta_header_extra_meta(client_class):
56 | class DummyNodeWithMeta(DummyNode):
57 | _CLIENT_META_HTTP_CLIENT = ("dm", "1.2.3")
58 |
59 | client = client_class(node_class=DummyNodeWithMeta)
60 | client._client_meta = (("h", "pg"),)
61 | client.perform_request("GET", "/")
62 |
63 | calls = client.transport.node_pool.get().calls
64 | assert len(calls) == 1
65 | headers = calls[0][1]["headers"]
66 | assert re.match(
67 | r"^ent=[0-9.]+p?,py=[0-9.]+p?,t=[0-9.]+p?,dm=[0-9.]+,h=pg?$",
68 | headers["x-elastic-client-meta"],
69 | )
70 |
71 |
72 | def test_client_meta_header_type_error(client_class):
73 | with pytest.raises(TypeError) as e:
74 | client_class(meta_header=1)
75 | assert str(e.value) == "meta_header must be of type bool"
76 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
98 | __pypackages__/
99 |
100 | # Celery stuff
101 | celerybeat-schedule
102 | celerybeat.pid
103 |
104 | # SageMath parsed files
105 | *.sage.py
106 |
107 | # Environments
108 | .env
109 | .venv
110 | env/
111 | venv/
112 | ENV/
113 | env.bak/
114 | venv.bak/
115 |
116 | # Spyder project settings
117 | .spyderproject
118 | .spyproject
119 |
120 | # Rope project settings
121 | .ropeproject
122 |
123 | # mkdocs documentation
124 | /site
125 |
126 | # mypy
127 | .mypy_cache/
128 | .dmypy.json
129 | dmypy.json
130 |
131 | # Pyre type checker
132 | .pyre/
133 |
134 | # pytype static type analyzer
135 | .pytype/
136 |
137 | # Cython debug symbols
138 | cython_debug/
139 |
140 | # JUnit
141 | junit/
142 |
143 | # CI output
144 | .ci/output
145 |
--------------------------------------------------------------------------------
/tests/client/app_search/cassettes/test_index_documents.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '[{"nps_link":"https://www.nps.gov/zion/index.htm","title":"Zion","date_established":"1919-11-19T06:00:00+00:00","world_heritage_site":"false","states":["Utah"],"description":"Located
4 | at the junction of the Colorado Plateau, Great Basin, and Mojave Desert, this
5 | park contains sandstone features such as mesas, rock towers, and canyons, including
6 | the Virgin River Narrows. The various sandstone formations and the forks of
7 | the Virgin River create a wilderness divided into four ecosystems: desert, riparian,
8 | woodland, and coniferous forest.","visitors":4295127.0,"id":"park_zion","location":"37.3,-113.05","square_km":595.8,"acres":147237.02},{"nps_link":"https://www.nps.gov/yell/index.htm","title":"Yellowstone","date_established":"1872-03-01T06:00:00+00:00","world_heritage_site":"true","states":["Wyoming","Montana","Idaho"],"description":"Situated
9 | on the Yellowstone Caldera, the park has an expansive network of geothermal
10 | areas including boiling mud pots, vividly colored hot springs such as Grand
11 | Prismatic Spring, and regularly erupting geysers, the best-known being Old Faithful.
12 | The yellow-hued Grand Canyon of the Yellowstone River contains several high
13 | waterfalls, while four mountain ranges traverse the park. More than 60 mammal
14 | species including gray wolves, grizzly bears, black bears, lynxes, bison, and
15 | elk, make this park one of the best wildlife viewing spots in the country.","visitors":4257177.0,"id":"park_yellowstone","location":"44.6,-110.5","square_km":8983.2,"acres":2219790.71}]'
16 | headers:
17 | accept:
18 | - application/json
19 | authorization:
20 | - Bearer private-ybzoyx7cok65hpxyxkwaarnn
21 | connection:
22 | - keep-alive
23 | content-type:
24 | - application/json
25 | method: POST
26 | uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/as/v1/engines/national-parks-demo/documents
27 | response:
28 | body:
29 | string: '[{"id":"park_zion","errors":[]},{"id":"park_yellowstone","errors":[]}]'
30 | headers:
31 | Cache-Control:
32 | - max-age=0, private, must-revalidate
33 | Content-Length:
34 | - '70'
35 | Content-Type:
36 | - application/json;charset=utf-8
37 | Date:
38 | - Thu, 10 Mar 2022 21:32:21 GMT
39 | Etag:
40 | - W/"bc9506a8cb5ce5a39da0ec392c1c4c7c"
41 | Server:
42 | - Jetty(9.4.43.v20210629)
43 | Vary:
44 | - Origin
45 | - Accept-Encoding, User-Agent
46 | X-App-Search-Version:
47 | - 8.1.0
48 | X-Cloud-Request-Id:
49 | - ZB0YLI8ITG2ArbAU4EJ0tg
50 | X-Found-Handling-Cluster:
51 | - efbb93a5d1bb4b3f90f192c495ee00d1
52 | X-Found-Handling-Instance:
53 | - instance-0000000000
54 | X-Request-Id:
55 | - ZB0YLI8ITG2ArbAU4EJ0tg
56 | X-Runtime:
57 | - '0.081390'
58 | status:
59 | code: 200
60 | message: OK
61 | version: 1
62 |
--------------------------------------------------------------------------------
/.buildkite/run-enterprise-search.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #
3 | # Launch one App Search node via the Docker image,
4 | # to form a cluster suitable for running the REST API tests.
5 | #
6 | # Export the STACK_VERSION variable, eg. '8.0.0-SNAPSHOT'.
7 |
8 | # Version 1.1.0
9 | # - Initial version of the run-app-search.sh script
10 | # - Refactored .buildkite version
11 |
12 | script_path=$(dirname $(realpath -s $0))
13 | source $script_path/functions/imports.sh
14 | set -euo pipefail
15 |
16 | CONTAINER_NAME=${CONTAINER_NAME-app-search}
17 | APP_SEARCH_SECRET_SESSION_KEY=${APP_SEARCH_SECRET_SESSION_KEY-int_test_secret}
18 |
19 | echo -e "\033[34;1mINFO:\033[0m Take down node if called twice with the same arguments (DETACH=true) or on seperate terminals \033[0m"
20 | cleanup_node $CONTAINER_NAME
21 |
22 | http_port=3002
23 | url=http://127.0.0.1:${http_port}
24 |
25 | # Pull the container, retry on failures up to 5 times with
26 | # short delays between each attempt. Fixes most transient network errors.
27 | docker_pull_attempts=0
28 | until [ "$docker_pull_attempts" -ge 5 ]
29 | do
30 | docker pull docker.elastic.co/enterprise-search/enterprise-search:"$STACK_VERSION" && break
31 | docker_pull_attempts=$((docker_pull_attempts+1))
32 | echo "Failed to pull image, retrying in 10 seconds (retry $docker_pull_attempts/5)..."
33 | sleep 10
34 | done
35 |
36 | echo -e "\033[34;1mINFO:\033[0m Starting container $CONTAINER_NAME \033[0m"
37 | set -x
38 | docker run \
39 | --name "$CONTAINER_NAME" \
40 | --network "$network_name" \
41 | --env "elasticsearch.host=$elasticsearch_url" \
42 | --env "elasticsearch.username=elastic" \
43 | --env "elasticsearch.password=$elastic_password" \
44 | --env "ENT_SEARCH_DEFAULT_PASSWORD=$elastic_password" \
45 | --env "secret_management.encryption_keys=[$APP_SEARCH_SECRET_SESSION_KEY]" \
46 | --env "enterprise_search.listen_port=$http_port" \
47 | --env "log_level=info" \
48 | --env "hide_version_info=false" \
49 | --env "worker.threads=2" \
50 | --env "allow_es_settings_modification=true" \
51 | --env "JAVA_OPTS=-Xms1g -Xmx2g" \
52 | --env "elasticsearch.ssl.enabled=true" \
53 | --env "elasticsearch.ssl.verify=true" \
54 | --env "elasticsearch.ssl.certificate=/usr/share/app-search/config/certs/testnode.crt" \
55 | --env "elasticsearch.ssl.certificate_authority=/usr/share/app-search/config/certs/ca.crt" \
56 | --env "elasticsearch.ssl.key=/usr/share/app-search/config/certs/testnode.key" \
57 | --volume $ssl_cert:/usr/share/app-search/config/certs/testnode.crt \
58 | --volume $ssl_key:/usr/share/app-search/config/certs/testnode.key \
59 | --volume $ssl_ca:/usr/share/app-search/config/certs/ca.crt \
60 | --publish "$http_port":3002 \
61 | --detach="$DETACH" \
62 | --health-cmd="curl --insecure --fail ${url} || exit 1" \
63 | --health-interval=30s \
64 | --health-retries=50 \
65 | --health-timeout=10s \
66 | --rm \
67 | docker.elastic.co/enterprise-search/enterprise-search:"$STACK_VERSION";
68 |
69 | if wait_for_container "$CONTAINER_NAME" "$network_name"; then
70 | echo -e "\033[32;1mSUCCESS:\033[0m Running on: ${url}\033[0m"
71 | fi
72 |
--------------------------------------------------------------------------------
/tests/client/app_search/cassettes/test_list_documents.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: null
4 | headers:
5 | accept:
6 | - application/json
7 | authorization:
8 | - Bearer private-ybzoyx7cok65hpxyxkwaarnn
9 | connection:
10 | - keep-alive
11 | method: GET
12 | uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/as/v1/engines/national-parks-demo/documents/list?page%5Bcurrent%5D=3&page%5Bsize%5D=2
13 | response:
14 | body:
15 | string: '{"meta":{"page":{"current":3,"total_pages":30,"total_results":59,"size":2}},"results":[{"visitors":4295127.0,"square_km":595.8,"date_established":"1919-11-19T06:00:00+00:00","world_heritage_site":"false","description":"Located
16 | at the junction of the Colorado Plateau, Great Basin, and Mojave Desert, this
17 | park contains sandstone features such as mesas, rock towers, and canyons,
18 | including the Virgin River Narrows. The various sandstone formations and the
19 | forks of the Virgin River create a wilderness divided into four ecosystems:
20 | desert, riparian, woodland, and coniferous forest.","location":"37.3,-113.05","acres":147237.02,"id":"park_zion","title":"Zion","nps_link":"https://www.nps.gov/zion/index.htm","states":["Utah"]},{"visitors":4257177.0,"square_km":8983.2,"date_established":"1872-03-01T06:00:00+00:00","world_heritage_site":"true","description":"Situated
21 | on the Yellowstone Caldera, the park has an expansive network of geothermal
22 | areas including boiling mud pots, vividly colored hot springs such as Grand
23 | Prismatic Spring, and regularly erupting geysers, the best-known being Old
24 | Faithful. The yellow-hued Grand Canyon of the Yellowstone River contains several
25 | high waterfalls, while four mountain ranges traverse the park. More than 60
26 | mammal species including gray wolves, grizzly bears, black bears, lynxes,
27 | bison, and elk, make this park one of the best wildlife viewing spots in the
28 | country.","location":"44.6,-110.5","acres":2219790.71,"id":"park_yellowstone","title":"Yellowstone","nps_link":"https://www.nps.gov/yell/index.htm","states":["Wyoming","Montana","Idaho"]}]}'
29 | headers:
30 | Cache-Control:
31 | - max-age=0, private, must-revalidate
32 | Content-Length:
33 | - '1592'
34 | Content-Type:
35 | - application/json;charset=utf-8
36 | Date:
37 | - Thu, 10 Mar 2022 21:32:20 GMT
38 | Etag:
39 | - W/"96bde9047efd604df9f8ff0a771e7910"
40 | Server:
41 | - Jetty(9.4.43.v20210629)
42 | Vary:
43 | - Origin
44 | - Accept-Encoding, User-Agent
45 | X-App-Search-Version:
46 | - 8.1.0
47 | X-Cloud-Request-Id:
48 | - Lwxk1onVT_WpCsSwptErDA
49 | X-Found-Handling-Cluster:
50 | - efbb93a5d1bb4b3f90f192c495ee00d1
51 | X-Found-Handling-Instance:
52 | - instance-0000000000
53 | X-Request-Id:
54 | - Lwxk1onVT_WpCsSwptErDA
55 | X-Runtime:
56 | - '0.510496'
57 | status:
58 | code: 200
59 | message: OK
60 | version: 1
61 |
--------------------------------------------------------------------------------
/tests/client/enterprise_search/cassettes/test_get_health.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: null
4 | headers:
5 | accept:
6 | - application/json
7 | authorization:
8 | - Basic ZWxhc3RpYzp5cWNHcFJxVTlNazRGUW1zdkpLeEw5VW8=
9 | connection:
10 | - keep-alive
11 | method: GET
12 | uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/ent/v1/internal/health
13 | response:
14 | body:
15 | string: '{"name":"8a5386c434c2","version":{"number":"8.1.0","build_hash":"233d9108d258845ddd4a36915d45e22c19024981","build_date":"2022-03-03T14:31:36+00:00"},"jvm":{"gc":{"collection_count":35,"collection_time":5263,"garbage_collectors":{"Copy":{"collection_count":30,"collection_time":2813},"MarkSweepCompact":{"collection_count":5,"collection_time":2450}}},"pid":8,"uptime":6514644,"memory_usage":{"heap_init":1289748480,"heap_used":594640040,"heap_committed":1246756864,"heap_max":1246756864,"object_pending_finalization_count":0,"non_heap_init":7667712,"non_heap_committed":308342784},"memory_pools":["CodeHeap
16 | \u0027non-nmethods\u0027","Metaspace","Tenured Gen","CodeHeap \u0027profiled
17 | nmethods\u0027","Eden Space","Survivor Space","Compressed Class Space","CodeHeap
18 | \u0027non-profiled nmethods\u0027"],"threads":{"thread_count":23,"peak_thread_count":32,"total_started_thread_count":72,"daemon_thread_count":13},"vm_version":"11.0.13+8-Ubuntu-0ubuntu1.20.04","vm_vendor":"Ubuntu","vm_name":"OpenJDK
19 | 64-Bit Server VM"},"filebeat":{"pid":72,"alive":true,"restart_count":0,"seconds_since_last_restart":-1},"metricbeat":{"alive":false},"esqueues_me":{"instance":{"created_at":1646951624033,"scheduled_at":1646951624033,"processing_started_at":1646951624090,"processing_latency":57,"total_processed":17,"time_since_last_scheduled":1778919,"time_since_last_processed":1778862},"Work::Engine::EngineDestroyer":{"created_at":1646951624033,"scheduled_at":1646951624033,"processing_started_at":1646951624090,"processing_latency":57,"total_processed":17,"time_since_last_scheduled":1778919,"time_since_last_processed":1778862}},"crawler":{"running":true,"workers":{"pool_size":2,"active":0,"available":2}},"system":{"java_version":"11.0.13","jruby_version":"9.2.16.0","os_name":"Linux","os_version":"5.4.0-1032-gcp"},"cluster_uuid":"B1R0DzFmRKCoGl4ib42t7w"}'
20 | headers:
21 | Cache-Control:
22 | - max-age=0, private, must-revalidate
23 | Content-Length:
24 | - '1844'
25 | Content-Type:
26 | - application/json;charset=utf-8
27 | Date:
28 | - Thu, 10 Mar 2022 23:03:22 GMT
29 | Etag:
30 | - W/"623d427d834003e23852837621bb41a6"
31 | Server:
32 | - Jetty(9.4.43.v20210629)
33 | Vary:
34 | - Accept-Encoding, User-Agent
35 | X-Cloud-Request-Id:
36 | - k1G5hXjDSRKkCnfeaWVjUw
37 | X-Found-Handling-Cluster:
38 | - efbb93a5d1bb4b3f90f192c495ee00d1
39 | X-Found-Handling-Instance:
40 | - instance-0000000000
41 | X-Request-Id:
42 | - k1G5hXjDSRKkCnfeaWVjUw
43 | X-Runtime:
44 | - '0.123531'
45 | status:
46 | code: 200
47 | message: OK
48 | version: 1
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # :warning: App Search and Workplace Search will be discontinued in 9.0
2 |
3 | Starting with Elastic version 9.0, the standalone Enterprise Search products, will no longer be included in our offering.
4 | They remain supported in their current form in version 8.x and will only receive security upgrades and fixes.
5 | Enterprise Search clients will continue to be supported in their current form throughout 8.x versions, according to our [EOL policy](https://www.elastic.co/support/eol).
6 | We recommend transitioning to our actively developed [Elastic Stack](https://www.elastic.co/elastic-stack) tools for your search use cases. However, if you're still using any Enterprise Search products, we recommend using the latest stable release of the clients.
7 |
8 | Here are some useful links with more information:
9 |
10 | - [Enterprise Search FAQ](https://www.elastic.co/resources/enterprise-search/enterprise-search-faq)
11 | - [One stop shop for Upgrading to Elastic Search 9](https://www.elastic.co/guide/en/enterprise-search/current/upgrading-to-9-x.html)
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Official Python client for Elastic Enterprise Search, App Search, and Workplace Search
27 |
28 | ## Installation
29 |
30 | The package can be installed from [PyPI](https://pypi.org/project/elastic-enterprise-search):
31 |
32 | ```bash
33 | $ python -m pip install elastic-enterprise-search
34 | ```
35 |
36 | The version follows the Elastic Stack version so `7.11` is compatible
37 | with Enterprise Search released in Elastic Stack 7.11.
38 |
39 | ## Documentation
40 |
41 | [See the documentation](https://www.elastic.co/guide/en/enterprise-search-clients/python) for how to get started,
42 | compatibility info, configuring, and an API reference.
43 |
44 | ## Contributing
45 |
46 | If you'd like to make a contribution to `enterprise-search-python` we
47 | provide [contributing documentation](https://github.com/elastic/enterprise-search-python/tree/main/CONTRIBUTING.md)
48 | to ensure your first contribution goes smoothly.
49 |
50 | ## License
51 |
52 | `enterprise-search-python` is available under the Apache-2.0 license.
53 | For more details see [LICENSE](https://github.com/elastic/enterprise-search-python/blob/main/LICENSE).
54 |
--------------------------------------------------------------------------------
/tests/client/enterprise_search/test_enterprise_search.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import pytest
19 |
20 | from elastic_enterprise_search import EnterpriseSearch
21 |
22 |
23 | @pytest.fixture()
24 | def enterprise_search():
25 | yield EnterpriseSearch(
26 | "https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io:443",
27 | basic_auth=("elastic", "yqcGpRqU9Mk4FQmsvJKxL9Uo"),
28 | )
29 |
30 |
31 | @pytest.mark.vcr()
32 | def test_get_stats(enterprise_search):
33 | resp = enterprise_search.get_stats()
34 | assert resp.meta.status == 200
35 | assert sorted(resp.keys()) == [
36 | "app",
37 | "cluster_uuid",
38 | "connectors",
39 | "crawler",
40 | "http",
41 | "product_usage",
42 | "queues",
43 | ]
44 |
45 | resp = enterprise_search.get_stats(include=["connectors", "queues"])
46 | assert resp.meta.status == 200
47 | assert sorted(resp.keys()) == ["connectors", "queues"]
48 |
49 |
50 | @pytest.mark.vcr()
51 | def test_get_health(enterprise_search):
52 | resp = enterprise_search.get_health()
53 | assert resp.meta.status == 200
54 | assert sorted(resp.keys()) == [
55 | "cluster_uuid",
56 | "crawler",
57 | "esqueues_me",
58 | "filebeat",
59 | "jvm",
60 | "metricbeat",
61 | "name",
62 | "system",
63 | "version",
64 | ]
65 | assert resp["version"] == {
66 | "number": "8.1.0",
67 | "build_hash": "233d9108d258845ddd4a36915d45e22c19024981",
68 | "build_date": "2022-03-03T14:31:36+00:00",
69 | }
70 |
71 |
72 | @pytest.mark.vcr()
73 | def test_get_version(enterprise_search):
74 | resp = enterprise_search.get_version()
75 | assert resp.meta.status == 200
76 | assert resp == {
77 | "number": "8.1.0",
78 | "build_hash": "233d9108d258845ddd4a36915d45e22c19024981",
79 | "build_date": "2022-03-03T14:31:36+00:00",
80 | }
81 |
82 |
83 | @pytest.mark.vcr()
84 | def test_get_and_put_read_only(enterprise_search):
85 | resp = enterprise_search.put_read_only(body={"enabled": True})
86 | assert resp.meta.status == 200
87 | assert resp == {"enabled": True}
88 |
89 | resp = enterprise_search.get_read_only()
90 | assert resp.meta.status == 200
91 | assert resp == {"enabled": True}
92 |
93 | resp = enterprise_search.put_read_only(enabled=False)
94 | assert resp.meta.status == 200
95 | assert resp == {"enabled": False}
96 |
--------------------------------------------------------------------------------
/noxfile.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import os
19 | from os.path import abspath, dirname, join
20 |
21 | import nox
22 |
23 | SOURCE_FILES = (
24 | "noxfile.py",
25 | "setup.py",
26 | "elastic_enterprise_search/",
27 | "utils/",
28 | "tests/",
29 | )
30 | # Allow building aiohttp when no wheels are available (eg. for recent Python versions)
31 | INSTALL_ENV = {"AIOHTTP_NO_EXTENSIONS": "1"}
32 |
33 |
34 | @nox.session(python="3.12")
35 | def format(session):
36 | session.install("black~=24.0", "isort", "flynt", "unasync", "setuptools")
37 |
38 | session.run("python", "utils/run-unasync.py")
39 | session.run("isort", "--profile=black", *SOURCE_FILES)
40 | session.run("flynt", *SOURCE_FILES)
41 | session.run("black", "--target-version=py36", *SOURCE_FILES)
42 | session.run("python", "utils/license-headers.py", "fix", *SOURCE_FILES)
43 |
44 | lint(session)
45 |
46 |
47 | @nox.session(python="3.12")
48 | def lint(session):
49 | session.install("flake8", "black~=24.0", "isort")
50 | session.run("black", "--check", "--target-version=py36", *SOURCE_FILES)
51 | session.run("isort", "--check", *SOURCE_FILES)
52 | session.run("flake8", "--ignore=E501,W503,E203", *SOURCE_FILES)
53 | session.run("python", "utils/license-headers.py", "check", *SOURCE_FILES)
54 |
55 |
56 | def tests_impl(session):
57 | job_id = os.environ.get("BUILDKITE_JOB_ID", None)
58 | if job_id is not None:
59 | junit_xml = join(
60 | abspath(dirname(__file__)),
61 | f"junit/{job_id}-junit.xml",
62 | )
63 | else:
64 | junit_xml = join(
65 | abspath(dirname(__file__)),
66 | "junit/enterprise-search-python-junit.xml",
67 | )
68 |
69 | session.install(
70 | ".[develop]",
71 | # https://github.com/elastic/elastic-transport-python/pull/121 broke the VCRpy cassettes on Python 3.10+
72 | "elastic-transport<8.10",
73 | env=INSTALL_ENV,
74 | silent=False,
75 | )
76 | session.run(
77 | "pytest",
78 | f"--junitxml={junit_xml}",
79 | "--cov=elastic_enterprise_search",
80 | "-ra", # report all except passes
81 | *(session.posargs or ("tests/",)),
82 | env={"PYTHONWARNINGS": "always::DeprecationWarning"},
83 | )
84 | session.run("coverage", "report", "-m")
85 |
86 |
87 | @nox.session(python=["3.6", "3.7", "3.8", "3.9", "3.10", "3.11", "3.12"])
88 | def test(session):
89 | tests_impl(session)
90 |
--------------------------------------------------------------------------------
/utils/bump-version.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | """Command line tool which changes the branch to be
19 | ready to build and test the given Elastic stack version.
20 | """
21 |
22 | import re
23 | import sys
24 | from pathlib import Path
25 |
26 | SOURCE_DIR = Path(__file__).absolute().parent.parent
27 |
28 |
29 | def find_and_replace(path, pattern, replace):
30 | # Does a find and replace within a file path and complains
31 | # if the given pattern isn't found in the file.
32 | with open(path, "r") as f:
33 | old_data = f.read()
34 |
35 | if re.search(pattern, old_data, flags=re.MULTILINE) is None:
36 | print(f"Didn't find the pattern {pattern!r} in {path!s}")
37 | exit(1)
38 |
39 | new_data = re.sub(pattern, replace, old_data, flags=re.MULTILINE)
40 | with open(path, "w") as f:
41 | f.truncate()
42 | f.write(new_data)
43 |
44 |
45 | def main():
46 | if len(sys.argv) != 2:
47 | print("usage: utils/bump-version.py [stack version]")
48 | exit(1)
49 |
50 | stack_version = sys.argv[1]
51 | try:
52 | python_version = re.search(r"^([0-9][0-9\.]*[0-9]+)", stack_version).group(1)
53 | except AttributeError:
54 | print(f"Couldn't match the given stack version {stack_version!r}")
55 | exit(1)
56 |
57 | # Pad the version value with .0 until there
58 | # we have the major, minor, and patch.
59 | for _ in range(3):
60 | if len(python_version.split(".")) >= 3:
61 | break
62 | python_version += ".0"
63 |
64 | find_and_replace(
65 | path=SOURCE_DIR / "elastic_enterprise_search/_version.py",
66 | pattern=r"__version__ = \"[0-9]+[0-9\.]*[0-9](?:\+dev)?\"",
67 | replace=f'__version__ = "{python_version}"',
68 | )
69 |
70 | # These values should always be the 'major.minor-SNAPSHOT'
71 | major_minor_version = ".".join(python_version.split(".")[:2])
72 | find_and_replace(
73 | path=SOURCE_DIR / ".buildkite/pipeline.yml",
74 | pattern=r'STACK_VERSION:\s+\- "[0-9]+[0-9\.]*[0-9](?:\-SNAPSHOT)?"',
75 | replace=f'STACK_VERSION:\n - "{major_minor_version}.0-SNAPSHOT"',
76 | )
77 | find_and_replace(
78 | path=SOURCE_DIR / ".github/workflows/unified-release.yml",
79 | pattern=r'STACK_VERSION:\s+"[0-9]+[0-9\.]*[0-9](?:\-SNAPSHOT)?"',
80 | replace=f'STACK_VERSION: "{major_minor_version}-SNAPSHOT"',
81 | )
82 |
83 |
84 | if __name__ == "__main__":
85 | main()
86 |
--------------------------------------------------------------------------------
/tests/client/app_search/cassettes/test_search.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"query":"tree","page":{"size":2}}'
4 | headers:
5 | accept:
6 | - application/json
7 | authorization:
8 | - Bearer private-ybzoyx7cok65hpxyxkwaarnn
9 | connection:
10 | - keep-alive
11 | content-type:
12 | - application/json
13 | method: POST
14 | uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/as/v1/engines/national-parks-demo/search
15 | response:
16 | body:
17 | string: '{"meta":{"alerts":[],"warnings":[],"precision":2,"page":{"current":1,"total_pages":10,"total_results":20,"size":2},"engine":{"name":"national-parks-demo","type":"default"},"request_id":"HduTq2QERG2-xjFjx0JWhA"},"results":[{"visitors":{"raw":1.1312786E7},"square_km":{"raw":2114.2},"world_heritage_site":{"raw":"true"},"date_established":{"raw":"1934-06-15T05:00:00+00:00"},"description":{"raw":"The
18 | Great Smoky Mountains, part of the Appalachian Mountains, span a wide range
19 | of elevations, making them home to over 400 vertebrate species, 100 tree species,
20 | and 5000 plant species. Hiking is the park''s main attraction, with over 800
21 | miles (1,300 km) of trails, including 70 miles (110 km) of the Appalachian
22 | Trail. Other activities include fishing, horseback riding, and touring nearly
23 | 80 historic structures."},"location":{"raw":"35.68,-83.53"},"acres":{"raw":522426.88},"_meta":{"id":"park_great-smoky-mountains","engine":"national-parks-demo","score":1.6969186E7},"id":{"raw":"park_great-smoky-mountains"},"title":{"raw":"Great
24 | Smoky Mountains"},"nps_link":{"raw":"https://www.nps.gov/grsm/index.htm"},"states":{"raw":["Tennessee","North
25 | Carolina"]}},{"visitors":{"raw":5969811.0},"square_km":{"raw":4862.9},"world_heritage_site":{"raw":"true"},"date_established":{"raw":"1919-02-26T06:00:00+00:00"},"description":{"raw":"The
26 | Grand Canyon, carved by the mighty Colorado River, is 277 miles (446 km) long,
27 | up to 1 mile (1.6 km) deep, and up to 15 miles (24 km) wide. Millions of years
28 | of erosion have exposed the multicolored layers of the Colorado Plateau in
29 | mesas and canyon walls, visible from both the north and south rims, or from
30 | a number of trails that descend into the canyon itself."},"location":{"raw":"36.06,-112.14"},"acres":{"raw":1201647.03},"_meta":{"id":"park_grand-canyon","engine":"national-parks-demo","score":8954717.0},"id":{"raw":"park_grand-canyon"},"title":{"raw":"Grand
31 | Canyon"},"nps_link":{"raw":"https://www.nps.gov/grca/index.htm"},"states":{"raw":["Arizona"]}}]}'
32 | headers:
33 | Cache-Control:
34 | - max-age=0, private, must-revalidate
35 | Content-Length:
36 | - '1993'
37 | Content-Type:
38 | - application/json;charset=utf-8
39 | Date:
40 | - Thu, 10 Mar 2022 21:32:22 GMT
41 | Etag:
42 | - W/"bd80d5461a85f4071c9a65b84f6546ba"
43 | Server:
44 | - Jetty(9.4.43.v20210629)
45 | Vary:
46 | - Origin
47 | - Accept-Encoding, User-Agent
48 | X-App-Search-Version:
49 | - 8.1.0
50 | X-Cloud-Request-Id:
51 | - HduTq2QERG2-xjFjx0JWhA
52 | X-Found-Handling-Cluster:
53 | - efbb93a5d1bb4b3f90f192c495ee00d1
54 | X-Found-Handling-Instance:
55 | - instance-0000000000
56 | X-Request-Id:
57 | - HduTq2QERG2-xjFjx0JWhA
58 | X-Runtime:
59 | - '0.111842'
60 | X-St-Internal-Rails-Version:
61 | - 5.2.6
62 | status:
63 | code: 200
64 | message: OK
65 | version: 1
66 |
--------------------------------------------------------------------------------
/tests/conftest.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import os
19 | from typing import Tuple
20 |
21 | import pytest
22 | import urllib3
23 | from elastic_transport import ApiResponseMeta, BaseNode, HttpHeaders
24 |
25 | from elastic_enterprise_search import AppSearch, EnterpriseSearch, WorkplaceSearch
26 |
27 |
28 | @pytest.fixture(scope="module")
29 | def vcr_config():
30 | return {"filter_headers": ["user-agent", "x-elastic-client-meta"]}
31 |
32 |
33 | @pytest.fixture(params=[EnterpriseSearch, AppSearch, WorkplaceSearch])
34 | def client_class(request):
35 | return request.param
36 |
37 |
38 | @pytest.fixture(scope="session")
39 | def ent_search_url():
40 | url = "http://localhost:3002"
41 | urls_to_try = [
42 | "http://enterprise-search:3002",
43 | "http://localhost:3002",
44 | "http://127.0.0.1:3002",
45 | ]
46 | if "ENTERPRISE_SEARCH_URL" in os.environ:
47 | urls_to_try.insert(0, os.environ["ENTERPRISE_SEARCH_URL"])
48 | for try_url in urls_to_try:
49 | try:
50 | http = urllib3.PoolManager()
51 | # do not follow redirect to avoid hitting authenticated endpoints
52 | # we only need to check that Enterprise Search is up at this point
53 | http.request("GET", try_url, redirect=False)
54 | url = try_url
55 | break
56 | except Exception:
57 | continue
58 | else: # nobreak
59 | pytest.fail("No Enterprise Search instance running on 'localhost:3002'")
60 | return url
61 |
62 |
63 | @pytest.fixture(scope="session")
64 | def ent_search_basic_auth() -> Tuple[str, str]:
65 | try:
66 | yield ("elastic", os.environ["ENTERPRISE_SEARCH_PASSWORD"])
67 | except KeyError:
68 | pytest.skip("Skipped test because 'ENTERPRISE_SEARCH_PASSWORD' isn't set")
69 |
70 |
71 | @pytest.fixture(scope="session")
72 | def app_search_bearer_auth() -> str:
73 | try:
74 | yield os.environ["APP_SEARCH_PRIVATE_KEY"]
75 | except KeyError:
76 | pytest.skip("Skipped test because 'APP_SEARCH_PRIVATE_KEY' isn't set")
77 |
78 |
79 | class DummyNode(BaseNode):
80 | def __init__(self, node_config, **kwargs):
81 | self.exception = kwargs.pop("exception", None)
82 | self.resp_status, self.resp_data = kwargs.pop("status", 200), kwargs.pop(
83 | "data", "{}"
84 | )
85 | self.resp_headers = kwargs.pop("headers", {})
86 | self.calls = []
87 | super().__init__(node_config)
88 |
89 | def perform_request(self, *args, **kwargs):
90 | self.calls.append((args, kwargs))
91 | if self.exception:
92 | raise self.exception
93 | meta = ApiResponseMeta(
94 | status=self.resp_status,
95 | http_version="1.1",
96 | headers=HttpHeaders(self.resp_headers),
97 | duration=0.0,
98 | node=self.config,
99 | )
100 | return meta, self.resp_data
101 |
--------------------------------------------------------------------------------
/docs/guide/overview.asciidoc:
--------------------------------------------------------------------------------
1 | [[overview]]
2 | == Overview
3 |
4 | `enterprise-search-python` is the official Python client for Elastic
5 | Enterprise Search, App Search, and Workplace Search.
6 |
7 | [discrete]
8 | === Compatibility
9 |
10 | Current development happens in the `main` branch.
11 |
12 | The library is compatible with all Elastic Enterprise Search versions since `7.x`
13 | but you **have to use a matching major version**:
14 |
15 | For **Elastic Enterprise Search 7.0** and later, use the major version 7 (`7.x.y`) of the
16 | library.
17 |
18 | The recommended way to set your requirements in your `setup.py` or
19 | `requirements.txt` is::
20 |
21 | # Elastic Enterprise Search 7.x
22 | elastic-enterprise-search>=7,<8
23 |
24 | [discrete]
25 | === Example usage
26 |
27 | [source,python]
28 | ------------------------------------
29 | >>> from elastic_enterprise_search import EnterpriseSearch
30 |
31 | # Connecting to an instance on Elastic Cloud w/ username and password
32 | >>> ent_search = EnterpriseSearch(
33 | "https://<...>.ent-search.us-central1.gcp.cloud.es.io",
34 | http_auth=("elastic", ""),
35 | )
36 | >>> ent_search.get_version()
37 | {
38 | 'number': '7.10.0',
39 | 'build_hash': '9d6eb9f067b7d7090c541890c21f6a1e15f29c48',
40 | 'build_date': '2020-10-05T16:19:16Z'
41 | }
42 |
43 | # If you're only planning on using App Search you
44 | # can instantiate App Search namespaced client by itself:
45 | >>> from elastic_enterprise_search import AppSearch
46 |
47 | # Connecting to an instance on Elastic Cloud w/ an App Search private key
48 | >>> app_search = AppSearch(
49 | "https://<...>.ent-search.us-central1.gcp.cloud.es.io",
50 | bearer_auth="private-",
51 | )
52 | >>> app_search.index_documents(
53 | engine_name="national-parks",
54 | documents=[{
55 | "id": "yellowstone",
56 | "title": "Yellowstone National Park"
57 | }]
58 | )
59 | ------------------------------------
60 |
61 | [NOTE]
62 | All the API calls map the raw REST API as closely as possible, including
63 | the distinction between required and optional arguments to the calls. This
64 | means that the code makes distinction between positional and keyword arguments;
65 | **we recommend that people use keyword arguments for all calls for
66 | consistency and safety.**
67 |
68 | [discrete]
69 | ==== Using Python datetimes with timezones
70 |
71 | Python https://docs.python.org/3/library/datetime.html#datetime.datetime[`datetime.datetime`]
72 | objects are automatically serialized according to https://tools.ietf.org/html/rfc3339[RFC 3339]
73 | which requires a timezone to be included. We highly recommend using datetimes that
74 | are timezone-aware. When creating a datetime object, use the `tzinfo` or `tz` parameter
75 | along with https://dateutil.readthedocs.io[`python-dateutil`] to ensure proper
76 | timezones on serialized `datetime` objects.
77 |
78 | To get the current day and time in UTC you can do the following:
79 |
80 | [source,python]
81 | ------------------------------------
82 | import datetime
83 | from dateutil import tz
84 |
85 | now = datetime.datetime.now(tz=tz.UTC)
86 | ------------------------------------
87 |
88 | ⚠️ **Datetimes without timezone information will be serialized as if they were within
89 | the locally configured timezone.** This is in line with HTTP and RFC 3339 specs
90 | which state that datetimes without timezone information should be assumed to be local time.
91 |
92 | ⚠️ https://blog.ganssle.io/articles/2019/11/utcnow.html[**Do not use `datetime.datetime.utcnow()` or `utcfromtimestamp()`!**]
93 | These APIs don't add timezone information to the resulting datetime which causes the
94 | serializer to return incorrect results.
95 |
96 |
97 | [discrete]
98 | === License
99 |
100 | `enterprise-search-python` is available under the https://github.com/elastic/enterprise-search-python/blob/main/LICENSE[Apache-2.0 license].
101 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import os
19 | import re
20 |
21 | from setuptools import find_packages, setup
22 |
23 | base_dir = os.path.dirname(os.path.abspath(__file__))
24 | with open(os.path.join(base_dir, "elastic_enterprise_search/_version.py")) as f:
25 | version = re.search(r"__version__\s+=\s+\"([^\"]+)\"", f.read()).group(1)
26 |
27 | # Remove all raw HTML from README for long description
28 | with open(os.path.join(base_dir, "README.md")) as f:
29 | lines = f.read().split("\n")
30 | last_html_index = 0
31 | for i, line in enumerate(lines):
32 | if line == "
":
33 | last_html_index = i + 1
34 | long_description = "\n".join(lines[last_html_index:])
35 |
36 | packages = [
37 | package
38 | for package in find_packages()
39 | if package.startswith("elastic_enterprise_search")
40 | ]
41 |
42 | setup(
43 | name="elastic-enterprise-search",
44 | description=(
45 | "Official Python client for Elastic Enterprise "
46 | "Search, App Search, and Workplace Search"
47 | ),
48 | long_description=long_description,
49 | long_description_content_type="text/markdown",
50 | version=version,
51 | author="Elastic",
52 | author_email="support@elastic.co",
53 | maintainer="Clients Team",
54 | maintainer_email="clients-team@elastic.co",
55 | url="https://github.com/elastic/enterprise-search-python",
56 | project_urls={
57 | "Documentation": "https://www.elastic.co/guide/en/enterprise-search-clients/python/current/index.html",
58 | "Source Code": "https://github.com/elastic/enterprise-search-python",
59 | "Issue Tracker": "https://github.com/elastic/enterprise-search-python/issues",
60 | },
61 | packages=packages,
62 | install_requires=[
63 | "elastic-transport>=8.4,<9",
64 | "PyJWT>=1,<3",
65 | "python-dateutil>=2,<3",
66 | "six>=1.12",
67 | ],
68 | python_requires=">=3.6",
69 | extras_require={
70 | "requests": ["requests>=2.4, <3"],
71 | "develop": [
72 | "pytest",
73 | "pytest-asyncio",
74 | "pytest-cov",
75 | "pytest-mock",
76 | "pytest-vcr",
77 | "mock",
78 | "requests",
79 | "aiohttp",
80 | ],
81 | },
82 | classifiers=[
83 | "Development Status :: 5 - Production/Stable",
84 | "License :: OSI Approved :: Apache Software License",
85 | "Intended Audience :: Developers",
86 | "Operating System :: OS Independent",
87 | "Programming Language :: Python",
88 | "Programming Language :: Python :: 3",
89 | "Programming Language :: Python :: 3.6",
90 | "Programming Language :: Python :: 3.7",
91 | "Programming Language :: Python :: 3.8",
92 | "Programming Language :: Python :: 3.9",
93 | "Programming Language :: Python :: 3.10",
94 | "Programming Language :: Python :: 3.11",
95 | "Programming Language :: Python :: 3.12",
96 | "Programming Language :: Python :: Implementation :: CPython",
97 | "Programming Language :: Python :: Implementation :: PyPy",
98 | ],
99 | )
100 |
--------------------------------------------------------------------------------
/tests/client/enterprise_search/cassettes/test_get_and_put_read_only.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: '{"enabled":true}'
4 | headers:
5 | accept:
6 | - application/json
7 | authorization:
8 | - Basic ZWxhc3RpYzp5cWNHcFJxVTlNazRGUW1zdkpLeEw5VW8=
9 | connection:
10 | - keep-alive
11 | content-type:
12 | - application/json
13 | method: PUT
14 | uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/ent/v1/internal/read_only_mode
15 | response:
16 | body:
17 | string: '{"enabled":true}'
18 | headers:
19 | Cache-Control:
20 | - max-age=0, private, must-revalidate
21 | Content-Length:
22 | - '16'
23 | Content-Type:
24 | - application/json;charset=utf-8
25 | Date:
26 | - Thu, 10 Mar 2022 23:28:28 GMT
27 | Etag:
28 | - W/"26b3426b2593763c96d0890b4a77a0bb"
29 | Server:
30 | - Jetty(9.4.43.v20210629)
31 | Vary:
32 | - Accept-Encoding, User-Agent
33 | X-Cloud-Request-Id:
34 | - BVLodpndTgqLYCw7l5kdIg
35 | X-Found-Handling-Cluster:
36 | - efbb93a5d1bb4b3f90f192c495ee00d1
37 | X-Found-Handling-Instance:
38 | - instance-0000000000
39 | X-Request-Id:
40 | - BVLodpndTgqLYCw7l5kdIg
41 | X-Runtime:
42 | - '0.347579'
43 | status:
44 | code: 200
45 | message: OK
46 | - request:
47 | body: null
48 | headers:
49 | accept:
50 | - application/json
51 | authorization:
52 | - Basic ZWxhc3RpYzp5cWNHcFJxVTlNazRGUW1zdkpLeEw5VW8=
53 | connection:
54 | - keep-alive
55 | method: GET
56 | uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/ent/v1/internal/read_only_mode
57 | response:
58 | body:
59 | string: '{"enabled":true}'
60 | headers:
61 | Cache-Control:
62 | - max-age=0, private, must-revalidate
63 | Content-Length:
64 | - '16'
65 | Content-Type:
66 | - application/json;charset=utf-8
67 | Date:
68 | - Thu, 10 Mar 2022 23:28:29 GMT
69 | Etag:
70 | - W/"26b3426b2593763c96d0890b4a77a0bb"
71 | Server:
72 | - Jetty(9.4.43.v20210629)
73 | Vary:
74 | - Accept-Encoding, User-Agent
75 | X-Cloud-Request-Id:
76 | - TpAJAR7GTFO7Neb3GpmN8Q
77 | X-Found-Handling-Cluster:
78 | - efbb93a5d1bb4b3f90f192c495ee00d1
79 | X-Found-Handling-Instance:
80 | - instance-0000000000
81 | X-Request-Id:
82 | - TpAJAR7GTFO7Neb3GpmN8Q
83 | X-Runtime:
84 | - '0.027522'
85 | status:
86 | code: 200
87 | message: OK
88 | - request:
89 | body: '{"enabled":false}'
90 | headers:
91 | accept:
92 | - application/json
93 | authorization:
94 | - Basic ZWxhc3RpYzp5cWNHcFJxVTlNazRGUW1zdkpLeEw5VW8=
95 | connection:
96 | - keep-alive
97 | content-type:
98 | - application/json
99 | method: PUT
100 | uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/ent/v1/internal/read_only_mode
101 | response:
102 | body:
103 | string: '{"enabled":false}'
104 | headers:
105 | Cache-Control:
106 | - max-age=0, private, must-revalidate
107 | Content-Length:
108 | - '17'
109 | Content-Type:
110 | - application/json;charset=utf-8
111 | Date:
112 | - Thu, 10 Mar 2022 23:28:29 GMT
113 | Etag:
114 | - W/"5acf3ff77b4420677b5923071f303fac"
115 | Server:
116 | - Jetty(9.4.43.v20210629)
117 | Vary:
118 | - Accept-Encoding, User-Agent
119 | X-Cloud-Request-Id:
120 | - 5g4W_zvrTpO4KsBAu-3-Bg
121 | X-Found-Handling-Cluster:
122 | - efbb93a5d1bb4b3f90f192c495ee00d1
123 | X-Found-Handling-Instance:
124 | - instance-0000000000
125 | X-Request-Id:
126 | - 5g4W_zvrTpO4KsBAu-3-Bg
127 | X-Runtime:
128 | - '0.269852'
129 | status:
130 | code: 200
131 | message: OK
132 | version: 1
133 |
--------------------------------------------------------------------------------
/tests/client/app_search/test_search_es_search.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import pytest
19 | from elastic_transport.client_utils import DEFAULT
20 |
21 | from elastic_enterprise_search import AppSearch
22 | from tests.conftest import DummyNode
23 |
24 |
25 | def test_mock_request():
26 | client = AppSearch(node_class=DummyNode, meta_header=False)
27 | client.search_es_search(
28 | engine_name="test",
29 | params={"key": "val"},
30 | body={"k": ["v", 2]},
31 | analytics_query="analytics-query",
32 | )
33 |
34 | calls = client.transport.node_pool.get().calls
35 | assert len(calls) == 1
36 | assert calls[-1][1].pop("request_timeout") is DEFAULT
37 | assert calls[-1] == (
38 | (
39 | "POST",
40 | "/api/as/v0/engines/test/elasticsearch/_search?key=val",
41 | ),
42 | {
43 | "body": b'{"k":["v",2]}',
44 | "headers": {
45 | "accept": "application/json",
46 | "content-type": "application/json",
47 | "x-enterprise-search-analytics": "analytics-query",
48 | },
49 | },
50 | )
51 |
52 |
53 | @pytest.mark.parametrize("analytics_tags", ["a,b", ["a", "b"]])
54 | def test_analytics_tags(analytics_tags):
55 | client = AppSearch(node_class=DummyNode, meta_header=False)
56 | client.options(headers={"Extra": "value"}).search_es_search(
57 | engine_name="test", analytics_tags=analytics_tags
58 | )
59 |
60 | calls = client.transport.node_pool.get().calls
61 | assert len(calls) == 1
62 | assert calls[-1][1].pop("request_timeout") is DEFAULT
63 | assert calls[-1] == (
64 | (
65 | "POST",
66 | "/api/as/v0/engines/test/elasticsearch/_search",
67 | ),
68 | {
69 | "body": None,
70 | "headers": {
71 | "extra": "value",
72 | "accept": "application/json",
73 | "content-type": "application/json",
74 | "x-enterprise-search-analytics-tags": "a,b",
75 | },
76 | },
77 | )
78 |
79 |
80 | @pytest.mark.parametrize("param_value", [object(), 1, 2.0, (), [3]])
81 | def test_search_es_search_params_type_error(param_value):
82 | client = AppSearch(node_class=DummyNode)
83 |
84 | with pytest.raises(TypeError) as e:
85 | client.search_es_search(
86 | engine_name="test",
87 | params={"key": param_value},
88 | )
89 | assert str(e.value) == "Values for 'params' parameter must be of type 'str'"
90 |
91 |
92 | @pytest.mark.vcr()
93 | def test_search_es_search(app_search):
94 | resp = app_search.search_es_search(
95 | engine_name="elastic-docs",
96 | params={"sort": "_score"},
97 | body={"query": {"match": {"*": "client"}}},
98 | )
99 | assert resp.body == {
100 | "took": 0,
101 | "timed_out": False,
102 | "_shards": {"total": 1, "successful": 1, "skipped": 0, "failed": 0},
103 | "hits": {
104 | "total": {"value": 0, "relation": "eq"},
105 | "max_score": None,
106 | "hits": [],
107 | },
108 | }
109 |
--------------------------------------------------------------------------------
/tests/test_oauth.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import pytest
19 |
20 | from elastic_enterprise_search import WorkplaceSearch
21 |
22 | CLIENT_ID = "1f87f8f6df473c06af79f88d1747afcb92e530295fad0fde340487673bec6ca6"
23 | CLIENT_SECRET = "d26a2c9aaa5870e8d6bdf8169aaf21ce2d66ec2e0180ffc34a0390d254135311"
24 | REDIRECT_URI = "http://localhost:8000"
25 | CODE = "7186fec34911a182606d2ab7fc36ea0ed4b8c32fef9929235cd80294422204ca"
26 | REFRESH_TOKEN = "8be8a32c22f98a28d59cdd9d2c2028c97fa6367b77a1a41cc27f2264038ee8f3"
27 |
28 |
29 | @pytest.mark.parametrize(
30 | ["response_type", "expected"],
31 | [
32 | (
33 | "token",
34 | (
35 | "http://localhost:3002/ws/oauth/authorize?response_type=token&"
36 | "client_id=1f87f8f6df473c06af79f88d1747afcb92e530295fad0fde340487673bec6ca6"
37 | "&redirect_uri=http%3A%2F%2Flocalhost%3A8000"
38 | ),
39 | ),
40 | (
41 | "code",
42 | (
43 | "http://localhost:3002/ws/oauth/authorize?response_type=code&"
44 | "client_id=1f87f8f6df473c06af79f88d1747afcb92e530295fad0fde340487673bec6ca6&"
45 | "redirect_uri=http%3A%2F%2Flocalhost%3A8000"
46 | ),
47 | ),
48 | ],
49 | )
50 | def test_oauth_authorize_url(response_type, expected):
51 | client = WorkplaceSearch("http://localhost:3002")
52 |
53 | assert expected == client.oauth_authorize_url(
54 | response_type=response_type, client_id=CLIENT_ID, redirect_uri=REDIRECT_URI
55 | )
56 |
57 |
58 | def test_oauth_authorize_url_bad_input():
59 | client = WorkplaceSearch("http://localhost:3002")
60 |
61 | with pytest.raises(ValueError) as e:
62 | client.oauth_authorize_url(
63 | response_type="ye", client_id=CLIENT_ID, redirect_uri=REDIRECT_URI
64 | )
65 | assert (
66 | str(e.value)
67 | == "'response_type' must be either 'code' for confidential flowor 'token' for implicit flow"
68 | )
69 |
70 | with pytest.raises(TypeError) as e:
71 | client.oauth_authorize_url(
72 | response_type="token", client_id=1, redirect_uri=REDIRECT_URI
73 | )
74 | assert str(e.value) == "All parameters must be of type 'str'"
75 |
76 |
77 | def test_oauth_exchange_for_token_bad_input():
78 | client = WorkplaceSearch("http://localhost:3002")
79 |
80 | with pytest.raises(ValueError) as e:
81 | client.oauth_exchange_for_access_token(
82 | client_id=CLIENT_ID, client_secret=CLIENT_SECRET, redirect_uri=REDIRECT_URI
83 | )
84 | assert str(e.value) == "Either the 'code' or 'refresh_token' parameter must be used"
85 |
86 | with pytest.raises(ValueError) as e:
87 | client.oauth_exchange_for_access_token(
88 | client_id=CLIENT_ID,
89 | client_secret=CLIENT_SECRET,
90 | redirect_uri=REDIRECT_URI,
91 | code="hello",
92 | refresh_token="world",
93 | )
94 | assert (
95 | str(e.value) == "'code' and 'refresh_token' parameters are mutually exclusive"
96 | )
97 |
98 | with pytest.raises(TypeError) as e:
99 | client.oauth_exchange_for_access_token(
100 | client_id=1,
101 | client_secret=CLIENT_SECRET,
102 | redirect_uri=REDIRECT_URI,
103 | code=CODE,
104 | )
105 | assert str(e.value) == "All parameters must be of type 'str'"
106 |
--------------------------------------------------------------------------------
/elastic_enterprise_search/__init__.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | """Python Elastic Enterprise Search Client"""
19 |
20 | import re
21 | import warnings
22 |
23 | from elastic_transport import ConnectionError as ConnectionError
24 | from elastic_transport import ConnectionTimeout as ConnectionTimeout
25 | from elastic_transport import SerializationError as SerializationError
26 | from elastic_transport import TransportError as TransportError
27 | from elastic_transport import __version__ as _elastic_transport_version
28 |
29 | from ._async.client import AsyncAppSearch as AsyncAppSearch
30 | from ._async.client import AsyncEnterpriseSearch as AsyncEnterpriseSearch
31 | from ._async.client import AsyncWorkplaceSearch as AsyncWorkplaceSearch
32 | from ._serializer import JsonSerializer
33 | from ._sync.client import AppSearch as AppSearch
34 | from ._sync.client import EnterpriseSearch as EnterpriseSearch
35 | from ._sync.client import WorkplaceSearch as WorkplaceSearch
36 | from ._version import __version__ # noqa: F401
37 | from .exceptions import (
38 | ApiError,
39 | BadGatewayError,
40 | BadRequestError,
41 | ConflictError,
42 | ForbiddenError,
43 | GatewayTimeoutError,
44 | InternalServerError,
45 | NotFoundError,
46 | PayloadTooLargeError,
47 | ServiceUnavailableError,
48 | UnauthorizedError,
49 | )
50 |
51 | warnings.warn(
52 | "Starting with Elastic version 9.0, the standalone Enterprise Search products, will no longer be included in our offering. "
53 | "They remain supported in their current form in version 8.x and will only receive security upgrades and fixes. "
54 | "Enterprise Search clients will continue to be supported in their current form throughout 8.x versions, according to our EOL policy (https://www.elastic.co/support/eol)."
55 | "\n"
56 | "We recommend transitioning to our actively developed Elastic Stack (https://www.elastic.co/elastic-stack) tools for your search use cases. "
57 | "However, if you're still using any Enterprise Search products, we recommend using the latest stable release of the clients.",
58 | category=DeprecationWarning,
59 | stacklevel=2,
60 | )
61 |
62 | # Ensure that a compatible version of elastic-transport is installed.
63 | _version_groups = tuple(int(x) for x in re.search(r"^(\d+)\.(\d+)\.(\d+)", _elastic_transport_version).groups()) # type: ignore
64 | if _version_groups < (8, 4, 0) or _version_groups > (9, 0, 0):
65 | raise ImportError(
66 | "An incompatible version of elastic-transport is installed. Must be between "
67 | "v8.4.0 and v9.0.0. Install the correct version with the following command: "
68 | "$ python -m pip install 'elastic-transport>=8.4, <9'"
69 | )
70 |
71 | __all__ = [
72 | "ApiError",
73 | "AppSearch",
74 | "AsyncAppSearch",
75 | "AsyncEnterpriseSearch",
76 | "AsyncWorkplaceSearch",
77 | "BadGatewayError",
78 | "BadRequestError",
79 | "ConflictError",
80 | "ConnectionError",
81 | "ConnectionTimeout",
82 | "EnterpriseSearch",
83 | "ForbiddenError",
84 | "GatewayTimeoutError",
85 | "InternalServerError",
86 | "JsonSerializer",
87 | "MethodNotImplementedError",
88 | "NotFoundError",
89 | "PayloadTooLargeError",
90 | "PaymentRequiredError",
91 | "SerializationError",
92 | "ServiceUnavailableError",
93 | "TransportError",
94 | "UnauthorizedError",
95 | "WorkplaceSearch",
96 | ]
97 |
98 | # Aliases for compatibility with 7.x
99 | APIError = ApiError
100 | JSONSerializer = JsonSerializer
101 |
--------------------------------------------------------------------------------
/tests/client/test_auth.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import warnings
19 |
20 | import pytest
21 | from elastic_transport.client_utils import DEFAULT
22 |
23 | from elastic_enterprise_search import EnterpriseSearch, WorkplaceSearch
24 | from tests.conftest import DummyNode
25 |
26 |
27 | def test_http_auth_none(client_class):
28 | client = client_class(node_class=DummyNode, meta_header=False)
29 | client.perform_request("GET", "/")
30 |
31 | calls = client.transport.node_pool.get().calls
32 | assert len(calls) == 1 and "Authorization" not in calls[-1][1]["headers"]
33 |
34 | client = client_class(http_auth=None, node_class=DummyNode, meta_header=False)
35 | client.perform_request("GET", "/")
36 | assert len(calls) == 1 and "Authorization" not in calls[-1][1]["headers"]
37 |
38 |
39 | @pytest.mark.parametrize(
40 | ["auth_kwarg", "auth_value", "header_value"],
41 | [
42 | ("http_auth", ("user", "password"), "Basic dXNlcjpwYXNzd29yZA=="),
43 | ("http_auth", ("üser", "pӓssword"), "Basic w7xzZXI6cNOTc3N3b3Jk"),
44 | ("http_auth", "this-is-a-token", "Bearer this-is-a-token"),
45 | ("basic_auth", ("user", "password"), "Basic dXNlcjpwYXNzd29yZA=="),
46 | ("basic_auth", ("üser", "pӓssword"), "Basic w7xzZXI6cNOTc3N3b3Jk"),
47 | ("bearer_auth", "this-is-a-token", "Bearer this-is-a-token"),
48 | ],
49 | )
50 | def test_http_auth_set_and_get(client_class, auth_kwarg, auth_value, header_value):
51 | client = client_class(node_class=DummyNode, **{auth_kwarg: auth_value})
52 | client.perform_request("GET", "/")
53 |
54 | calls = client.transport.node_pool.get().calls
55 | assert len(calls) == 1
56 | assert calls[-1][1]["headers"]["Authorization"] == header_value
57 |
58 |
59 | def test_http_auth_per_request_override():
60 | client = EnterpriseSearch(http_auth="bad-token", node_class=DummyNode)
61 | with warnings.catch_warnings(record=True) as w:
62 | client.get_version(http_auth=("user", "password"))
63 |
64 | assert len(w) == 1 and str(w[0].message) == (
65 | "Passing transport options in the API method is deprecated. "
66 | "Use 'EnterpriseSearch.options()' instead."
67 | )
68 |
69 | calls = client.transport.node_pool.get().calls
70 | assert len(calls) == 1
71 | assert calls[-1][1]["headers"]["Authorization"] == "Basic dXNlcjpwYXNzd29yZA=="
72 |
73 |
74 | def test_http_auth_disable_with_none():
75 | client = EnterpriseSearch(bearer_auth="api-token", node_class=DummyNode)
76 | client.perform_request("GET", "/")
77 |
78 | calls = client.transport.node_pool.get().calls
79 | assert len(calls) == 1
80 | assert calls[-1][1]["headers"]["Authorization"] == "Bearer api-token"
81 |
82 | client.options(bearer_auth=None).get_version()
83 | assert len(calls) == 2
84 | assert "Authorization" not in calls[-1][1]["headers"]
85 |
86 | client.options(basic_auth=None).get_version()
87 | assert len(calls) == 3
88 | assert "Authorization" not in calls[-1][1]["headers"]
89 |
90 |
91 | @pytest.mark.parametrize("http_auth", ["token", ("user", "pass")])
92 | def test_auth_not_sent_with_oauth_exchange(http_auth):
93 | client = WorkplaceSearch(
94 | node_class=DummyNode, meta_header=False, http_auth=http_auth
95 | )
96 | client.oauth_exchange_for_access_token(
97 | client_id="client-id",
98 | client_secret="client-secret",
99 | redirect_uri="redirect-uri",
100 | code="code",
101 | )
102 |
103 | calls = client.transport.node_pool.get().calls
104 | assert calls == [
105 | (
106 | (
107 | "POST",
108 | "/ws/oauth/token?grant_type=authorization_code&client_id=client-id&client_secret=client-secret&redirect_uri=redirect-uri&code=code",
109 | ),
110 | {
111 | "body": None,
112 | "headers": {},
113 | "request_timeout": DEFAULT,
114 | },
115 | )
116 | ]
117 |
--------------------------------------------------------------------------------
/utils/license-headers.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | """Script which verifies that all source files have a license header.
19 | Has two modes: 'fix' and 'check'. 'fix' fixes problems, 'check' will
20 | error out if 'fix' would have changed the file.
21 | """
22 |
23 | import os
24 | import sys
25 | from itertools import chain
26 | from typing import Iterator, List
27 |
28 | lines_to_keep = ["# -*- coding: utf-8 -*-\n", "#!/usr/bin/env python\n"]
29 | license_header_lines = [
30 | "# Licensed to Elasticsearch B.V. under one or more contributor\n",
31 | "# license agreements. See the NOTICE file distributed with\n",
32 | "# this work for additional information regarding copyright\n",
33 | "# ownership. Elasticsearch B.V. licenses this file to you under\n",
34 | '# the Apache License, Version 2.0 (the "License"); you may\n',
35 | "# not use this file except in compliance with the License.\n",
36 | "# You may obtain a copy of the License at\n",
37 | "#\n",
38 | "# http://www.apache.org/licenses/LICENSE-2.0\n",
39 | "#\n",
40 | "# Unless required by applicable law or agreed to in writing,\n",
41 | "# software distributed under the License is distributed on an\n",
42 | '# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n',
43 | "# KIND, either express or implied. See the License for the\n",
44 | "# specific language governing permissions and limitations\n",
45 | "# under the License.\n",
46 | "\n",
47 | ]
48 |
49 |
50 | def find_files_to_fix(sources: List[str]) -> Iterator[str]:
51 | """Iterates over all files and dirs in 'sources' and returns
52 | only the filepaths that need fixing.
53 | """
54 | for source in sources:
55 | if os.path.isfile(source) and does_file_need_fix(source):
56 | yield source
57 | elif os.path.isdir(source):
58 | for root, _, filenames in os.walk(source):
59 | for filename in filenames:
60 | filepath = os.path.join(root, filename)
61 | if does_file_need_fix(filepath):
62 | yield filepath
63 |
64 |
65 | def does_file_need_fix(filepath: str) -> bool:
66 | if not filepath.endswith(".py"):
67 | return False
68 | with open(filepath, mode="r") as f:
69 | first_license_line = None
70 | for line in f:
71 | if line == license_header_lines[0]:
72 | first_license_line = line
73 | break
74 | elif line not in lines_to_keep:
75 | return True
76 | for header_line, line in zip(
77 | license_header_lines, chain((first_license_line,), f)
78 | ):
79 | if line != header_line:
80 | return True
81 | return False
82 |
83 |
84 | def add_header_to_file(filepath: str) -> None:
85 | with open(filepath, mode="r") as f:
86 | lines = list(f)
87 | i = 0
88 | for i, line in enumerate(lines):
89 | if line not in lines_to_keep:
90 | break
91 | lines = lines[:i] + license_header_lines + lines[i:]
92 | with open(filepath, mode="w") as f:
93 | f.truncate()
94 | f.write("".join(lines))
95 | print(f"Fixed {os.path.relpath(filepath, os.getcwd())}")
96 |
97 |
98 | def main():
99 | mode = sys.argv[1]
100 | assert mode in ("fix", "check")
101 | sources = [os.path.abspath(x) for x in sys.argv[2:]]
102 | files_to_fix = find_files_to_fix(sources)
103 |
104 | if mode == "fix":
105 | for filepath in files_to_fix:
106 | add_header_to_file(filepath)
107 | else:
108 | no_license_headers = list(files_to_fix)
109 | if no_license_headers:
110 | print("No license header found in:")
111 | cwd = os.getcwd()
112 | [
113 | print(f" - {os.path.relpath(filepath, cwd)}")
114 | for filepath in no_license_headers
115 | ]
116 | sys.exit(1)
117 | else:
118 | print("All files had license header")
119 |
120 |
121 | if __name__ == "__main__":
122 | main()
123 |
--------------------------------------------------------------------------------
/tests/client/enterprise_search/cassettes/test_get_stats.yaml:
--------------------------------------------------------------------------------
1 | interactions:
2 | - request:
3 | body: null
4 | headers:
5 | accept:
6 | - application/json
7 | authorization:
8 | - Basic ZWxhc3RpYzp5cWNHcFJxVTlNazRGUW1zdkpLeEw5VW8=
9 | connection:
10 | - keep-alive
11 | method: GET
12 | uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/ent/v1/internal/stats
13 | response:
14 | body:
15 | string: '{"cluster_uuid":"B1R0DzFmRKCoGl4ib42t7w","http":{"connections":{"current":6,"max":38,"total":1388},"request_duration_ms":{"max":72381,"mean":31457.7004341534,"std_dev":4976.998594428453},"network_bytes":{"received_total":1585843,"received_rate":0,"sent_total":5463819,"sent_rate":0},"responses":{"1xx":0,"2xx":2399,"3xx":0,"4xx":12,"5xx":0}},"app":{"pid":8,"start":"2022-03-10T23:11:06+00:00","end":"2022-03-10T23:12:06+00:00","metrics":{"timers.http.request.all":{"sum":258.47601890563965,"max":78.5210132598877,"mean":18.46257277897426},"timers.http.request.200":{"sum":258.47601890563965,"max":78.5210132598877,"mean":18.46257277897426},"timers.actastic.relation.search":{"sum":74.78302344679832,"max":8.070411160588264,"mean":4.985534896453221},"timers.actastic.relation.document_count":{"sum":33.80800597369671,"max":3.9616338908672333,"mean":3.380800597369671},"timers.cron.local.cron-refresh_elasticsearch_license.total_job_time":{"sum":7.745834067463875,"max":7.745834067463875,"mean":7.745834067463875},"timers.cron.local.cron-update_search_relevance_suggestions.total_job_time":{"sum":54.193636402487755,"max":54.193636402487755,"mean":54.193636402487755},"counters.http.request.all":14,"counters.http.request.200":14}},"queues":{"engine_destroyer":{"pending":0},"process_crawl":{"pending":0},"mailer":{"pending":0},"failed":[]},"connectors":{"alive":true,"pool":{"extract_worker_pool":{"running":true,"queue_depth":0,"size":1,"busy":1,"idle":0,"total_scheduled":1,"total_completed":0},"subextract_worker_pool":{"running":true,"queue_depth":0,"size":0,"busy":0,"idle":0,"total_scheduled":0,"total_completed":0},"publish_worker_pool":{"running":true,"queue_depth":0,"size":0,"busy":0,"idle":0,"total_scheduled":0,"total_completed":0}},"job_store":{"waiting":0,"working":0,"job_types":{"full":0,"incremental":0,"delete":0,"permissions":0}}},"crawler":{"global":{"crawl_requests":{"pending":0,"active":0,"successful":0,"failed":0}},"node":{"pages_visited":0,"urls_allowed":0,"urls_denied":{},"status_codes":{},"queue_size":{"primary":0,"purge":0},"active_threads":0,"workers":{"pool_size":2,"active":0,"available":2}}},"product_usage":{"app_search":{"total_engines":5},"workplace_search":{"total_org_sources":0,"total_private_sources":0,"total_queries_last_30_days":0}}}'
16 | headers:
17 | Cache-Control:
18 | - max-age=0, private, must-revalidate
19 | Content-Length:
20 | - '2277'
21 | Content-Type:
22 | - application/json;charset=utf-8
23 | Date:
24 | - Thu, 10 Mar 2022 23:13:00 GMT
25 | Etag:
26 | - W/"5fdee13797bbe623fee0bc75353be447"
27 | Server:
28 | - Jetty(9.4.43.v20210629)
29 | Vary:
30 | - Accept-Encoding, User-Agent
31 | X-Cloud-Request-Id:
32 | - 9t6TxfkYS9-xnrI_s1X7uQ
33 | X-Found-Handling-Cluster:
34 | - efbb93a5d1bb4b3f90f192c495ee00d1
35 | X-Found-Handling-Instance:
36 | - instance-0000000000
37 | X-Request-Id:
38 | - 9t6TxfkYS9-xnrI_s1X7uQ
39 | X-Runtime:
40 | - '0.148459'
41 | status:
42 | code: 200
43 | message: OK
44 | - request:
45 | body: null
46 | headers:
47 | accept:
48 | - application/json
49 | authorization:
50 | - Basic ZWxhc3RpYzp5cWNHcFJxVTlNazRGUW1zdkpLeEw5VW8=
51 | connection:
52 | - keep-alive
53 | method: GET
54 | uri: https://my-deployment-c6095a.ent.us-central1.gcp.cloud.es.io/api/ent/v1/internal/stats?include=connectors,queues
55 | response:
56 | body:
57 | string: '{"connectors":{"alive":true,"pool":{"extract_worker_pool":{"running":true,"queue_depth":0,"size":1,"busy":1,"idle":0,"total_scheduled":1,"total_completed":0},"subextract_worker_pool":{"running":true,"queue_depth":0,"size":0,"busy":0,"idle":0,"total_scheduled":0,"total_completed":0},"publish_worker_pool":{"running":true,"queue_depth":0,"size":0,"busy":0,"idle":0,"total_scheduled":0,"total_completed":0}},"job_store":{"waiting":0,"working":0,"job_types":{"full":0,"incremental":0,"delete":0,"permissions":0}}},"queues":{"engine_destroyer":{"pending":0},"process_crawl":{"pending":0},"mailer":{"pending":0},"failed":[]}}'
58 | headers:
59 | Cache-Control:
60 | - max-age=0, private, must-revalidate
61 | Content-Length:
62 | - '620'
63 | Content-Type:
64 | - application/json;charset=utf-8
65 | Date:
66 | - Thu, 10 Mar 2022 23:13:01 GMT
67 | Etag:
68 | - W/"5ebfdeaf9e3c855c552df3b56cd305c5"
69 | Server:
70 | - Jetty(9.4.43.v20210629)
71 | Vary:
72 | - Accept-Encoding, User-Agent
73 | X-Cloud-Request-Id:
74 | - gRT9G4f7Ryi9AsJupHocNA
75 | X-Found-Handling-Cluster:
76 | - efbb93a5d1bb4b3f90f192c495ee00d1
77 | X-Found-Handling-Instance:
78 | - instance-0000000000
79 | X-Request-Id:
80 | - gRT9G4f7Ryi9AsJupHocNA
81 | X-Runtime:
82 | - '0.079419'
83 | status:
84 | code: 200
85 | message: OK
86 | version: 1
87 |
--------------------------------------------------------------------------------
/.ci/make.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # ------------------------------------------------------- #
4 | #
5 | # Skeleton for common build entry script for all elastic
6 | # clients. Needs to be adapted to individual client usage.
7 | #
8 | # Must be called: ./.ci/make.sh
9 | #
10 | # Version: 1.1.0
11 | #
12 | # Targets:
13 | # ---------------------------
14 | # assemble : build client artefacts with version
15 | # bump : bump client internals to version
16 | # codegen : generate endpoints
17 | # docsgen : generate documentation
18 | # examplegen : generate the doc examples
19 | # clean : clean workspace
20 | #
21 | # ------------------------------------------------------- #
22 |
23 | # ------------------------------------------------------- #
24 | # Bootstrap
25 | # ------------------------------------------------------- #
26 |
27 | script_path=$(dirname "$(realpath -s "$0")")
28 | repo=$(realpath "$script_path/../")
29 |
30 | # shellcheck disable=SC1090
31 | CMD=$1
32 | TASK=$1
33 | TASK_ARGS=()
34 | VERSION=$2
35 | STACK_VERSION=$VERSION
36 | set -euo pipefail
37 |
38 | product="elastic/enterprise-search-python"
39 | output_folder=".ci/output"
40 | codegen_folder=".ci/output"
41 | OUTPUT_DIR="$repo/${output_folder}"
42 | REPO_BINDING="${OUTPUT_DIR}:/sln/${output_folder}"
43 | mkdir -p "$OUTPUT_DIR"
44 |
45 | echo -e "\033[34;1mINFO:\033[0m PRODUCT ${product}\033[0m"
46 | echo -e "\033[34;1mINFO:\033[0m VERSION ${STACK_VERSION}\033[0m"
47 | echo -e "\033[34;1mINFO:\033[0m OUTPUT_DIR ${OUTPUT_DIR}\033[0m"
48 |
49 | # ------------------------------------------------------- #
50 | # Parse Command
51 | # ------------------------------------------------------- #
52 |
53 | case $CMD in
54 | clean)
55 | echo -e "\033[36;1mTARGET: clean workspace $output_folder\033[0m"
56 | rm -rf "$output_folder"
57 | echo -e "\033[32;1mdone.\033[0m"
58 | exit 0
59 | ;;
60 | assemble)
61 | if [ -v $VERSION ]; then
62 | echo -e "\033[31;1mTARGET: assemble -> missing version parameter\033[0m"
63 | exit 1
64 | fi
65 | echo -e "\033[36;1mTARGET: assemble artefact $VERSION\033[0m"
66 | TASK=release
67 | TASK_ARGS=("$VERSION" "$output_folder")
68 | ;;
69 | codegen)
70 | if [ -v $VERSION ]; then
71 | echo -e "\033[31;1mTARGET: codegen -> missing version parameter\033[0m"
72 | exit 1
73 | fi
74 | echo -e "\033[36;1mTARGET: codegen API v$VERSION\033[0m"
75 | TASK=codegen
76 | # VERSION is BRANCH here for now
77 | TASK_ARGS=("$VERSION" "$codegen_folder")
78 | ;;
79 | docsgen)
80 | if [ -v $VERSION ]; then
81 | echo -e "\033[31;1mTARGET: docsgen -> missing version parameter\033[0m"
82 | exit 1
83 | fi
84 | echo -e "\033[36;1mTARGET: generate docs for $VERSION\033[0m"
85 | TASK=codegen
86 | # VERSION is BRANCH here for now
87 | TASK_ARGS=("$VERSION" "$codegen_folder")
88 | ;;
89 | examplesgen)
90 | echo -e "\033[36;1mTARGET: generate examples\033[0m"
91 | TASK=codegen
92 | # VERSION is BRANCH here for now
93 | TASK_ARGS=("$VERSION" "$codegen_folder")
94 | ;;
95 | bump)
96 | if [ -v $VERSION ]; then
97 | echo -e "\033[31;1mTARGET: bump -> missing version parameter\033[0m"
98 | exit 1
99 | fi
100 | echo -e "\033[36;1mTARGET: bump to version $VERSION\033[0m"
101 | TASK=bump
102 | # VERSION is BRANCH here for now
103 | TASK_ARGS=("$VERSION")
104 | ;;
105 | *)
106 | echo -e "\nUsage:\n\t $CMD is not supported right now\n"
107 | exit 1
108 | esac
109 |
110 |
111 | # ------------------------------------------------------- #
112 | # Build Container
113 | # ------------------------------------------------------- #
114 |
115 | echo -e "\033[34;1mINFO: building $product container\033[0m"
116 |
117 | docker build \
118 | --file $repo/.buildkite/Dockerfile \
119 | --tag ${product} \
120 | .
121 |
122 | # ------------------------------------------------------- #
123 | # Run the Container
124 | # ------------------------------------------------------- #
125 |
126 | echo -e "\033[34;1mINFO: running $product container\033[0m"
127 |
128 | if [[ "$CMD" == "assemble" ]]; then
129 |
130 | # Build dists into .ci/output
131 | docker run \
132 | --rm -v $repo/.ci/output:/code/enterprise-search-python/dist \
133 | $product \
134 | /bin/bash -c "python /code/enterprise-search-python/utils/build-dists.py $VERSION"
135 |
136 | # Verify that there are dists in .ci/output
137 | if compgen -G ".ci/output/*" > /dev/null; then
138 |
139 | # Tarball everything up in .ci/output
140 | cd $repo/.ci/output && tar -czvf enterprise-search-python-$VERSION.tar.gz * && cd -
141 |
142 | echo -e "\033[32;1mTARGET: successfully assembled client v$VERSION\033[0m"
143 | exit 0
144 | else
145 | echo -e "\033[31;1mTARGET: assemble failed, empty workspace!\033[0m"
146 | exit 1
147 | fi
148 | fi
149 |
150 | if [[ "$CMD" == "bump" ]]; then
151 | docker run \
152 | --rm -v $repo:/code/enterprise-search-python \
153 | $product \
154 | /bin/bash -c "python /code/enterprise-search-python/utils/bump-version.py $VERSION"
155 |
156 | exit 0
157 | fi
158 |
159 | if [[ "$CMD" == "codegen" ]]; then
160 | echo "TODO"
161 | fi
162 |
163 | if [[ "$CMD" == "docsgen" ]]; then
164 | echo "TODO"
165 | fi
166 |
167 | if [[ "$CMD" == "examplesgen" ]]; then
168 | echo "TODO"
169 | fi
170 |
171 | echo "Must be called with '.ci/make.sh [command]"
172 | exit 1
173 |
--------------------------------------------------------------------------------
/docs/guide/enterprise-search-api.asciidoc:
--------------------------------------------------------------------------------
1 | [[enterprise-search-apis]]
2 | == Enterprise Search APIs
3 |
4 | **On this page**
5 |
6 | * <>
7 | * <>
8 | * <>
9 | * <>
10 |
11 | [[enterprise-search-initializing]]
12 | === Initializing the Client
13 |
14 | Enterprise Search APIs are used for managing the Enterprise Search deployment.
15 |
16 | Some of the APIs require HTTP basic auth of a user on the Elasticsearch cluster
17 | that has access to managing the cluster.
18 |
19 | [source,python]
20 | ---------------
21 | from elastic_enterprise_search import EnterpriseSearch
22 |
23 | enterprise_search = EnterpriseSearch(
24 | "https://localhost:3002",
25 | http_auth=("elastic", "")
26 | )
27 | ---------------
28 |
29 | [[enterprise-search-health-api]]
30 | === Deployment Health API
31 |
32 | Checks the status and health of the Enterprise Search deployment
33 | using the `get_health()` method:
34 |
35 | [source,python]
36 | ---------------
37 | # Request:
38 | enterprise_search.get_health()
39 |
40 | # Response:
41 | {
42 | "esqueues_me": {
43 | "Work::Engine::EngineDestroyer": {
44 | "created_at": 1611783321211,
45 | "processing_latency": 1167,
46 | "processing_started_at": 1611783322377,
47 | "scheduled_at": 1611783321210,
48 | "time_since_last_processed": 70015969,
49 | "time_since_last_scheduled": 70017136
50 | },
51 | ...
52 | },
53 | "filebeat": {
54 | "alive": True,
55 | "pid": 134,
56 | "restart_count": 0,
57 | "seconds_since_last_restart": -1
58 | },
59 | "jvm": {
60 | "gc": {
61 | "collection_count": 149,
62 | "collection_time": 3534,
63 | "garbage_collectors": {
64 | "PS MarkSweep": {
65 | "collection_count": 5,
66 | "collection_time": 1265
67 | },
68 | "PS Scavenge": {
69 | "collection_count": 144,
70 | "collection_time": 2269
71 | }
72 | }
73 | },
74 | "memory_pools": [
75 | "Code Cache",
76 | "Metaspace",
77 | "Compressed Class Space",
78 | "PS Eden Space",
79 | "PS Survivor Space",
80 | "PS Old Gen"
81 | ],
82 | "memory_usage": {
83 | "heap_committed": 1786773504,
84 | "heap_init": 1073741824,
85 | "heap_max": 1908932608,
86 | "heap_used": 674225760,
87 | "non_heap_committed": 421683200,
88 | "non_heap_init": 2555904,
89 | "object_pending_finalization_count": 0
90 | },
91 | "pid": 6,
92 | "threads": {
93 | "daemon_thread_count": 23,
94 | "peak_thread_count": 54,
95 | "thread_count": 50,
96 | "total_started_thread_count": 840
97 | },
98 | "uptime": 41033501,
99 | "vm_name": "OpenJDK 64-Bit Server VM",
100 | "vm_vendor": "AdoptOpenJDK",
101 | "vm_version": "25.252-b09"
102 | },
103 | "name": "f1b653d1bbd8",
104 | "system": {
105 | "java_version": "1.8.0_252",
106 | "jruby_version": "9.2.13.0",
107 | "os_name": "Linux",
108 | "os_version": "5.4.0-54-generic"
109 | },
110 | "version": {
111 | "build_date": "2021-01-06T15:24:44Z",
112 | "build_hash": "3a6edf8029dd285b60f1a6d63c741f46df7f195f",
113 | "number": "7.12.0"
114 | }
115 | }
116 | ---------------
117 |
118 | [[enterprise-search-read-only-api]]
119 | === Read-Only Mode APIs
120 |
121 | You can get and set https://www.elastic.co/guide/en/enterprise-search/current/read-only-api.html[Read-Only Mode]
122 | with the `get_read_only()` and `put_read_only()` methods:
123 |
124 | [source,python]
125 | ---------------
126 | # Request:
127 | enterprise_search.get_read_only()
128 |
129 | # Response:
130 | {
131 | "enabled": False
132 | }
133 |
134 | # Request:
135 | enterprise_search.put_read_only(enabled=True)
136 |
137 | # Response:
138 | {
139 | "enabled": True
140 | }
141 |
142 | ---------------
143 |
144 | [[enterprise-search-stats-api]]
145 | === Deployment Stats API
146 |
147 | Gets stats about internal processes and data structures used within
148 | your Enterprise Search deployment using the `get_stats()` method:
149 |
150 | [source,python]
151 | ---------------
152 | # Request:
153 | enterprise_search.get_stats()
154 |
155 | # Response:
156 | {
157 | "app": {
158 | "end": "2021-01-28T17:06:43+00:00",
159 | "metrics": {
160 | "counters.http.request.302": 2,
161 | "counters.http.request.all": 2,
162 | "timers.actastic.relation.document_count": {
163 | "max": 1.8278780044056475,
164 | "mean": 1.5509582590311766,
165 | "sum": 6.203833036124706
166 | },
167 | "timers.actastic.relation.search": {
168 | "max": 8.630949014332145,
169 | "mean": 5.581304353922057,
170 | "sum": 189.76434803334996
171 | },
172 | "timers.http.request.302": {
173 | "max": 11.984109878540039,
174 | "mean": 11.151552200317383,
175 | "sum": 22.303104400634766
176 | },
177 | "timers.http.request.all": {
178 | "max": 11.984109878540039,
179 | "mean": 11.151552200317383,
180 | "sum": 22.303104400634766
181 | }
182 | },
183 | "pid": 6,
184 | "start": "2021-01-28T17:05:43+00:00"
185 | },
186 | "connectors": {
187 | "alive": True,
188 | "job_store": {
189 | "job_types": {
190 | "delete": 0,
191 | "full": 0,
192 | "incremental": 0,
193 | "permissions": 0
194 | },
195 | "waiting": 0,
196 | "working": 0
197 | },
198 | "pool": {
199 | "extract_worker_pool": {
200 | "busy": 1,
201 | "idle": 7,
202 | "queue_depth": 0,
203 | "running": True,
204 | "size": 8,
205 | "total_completed": 16286,
206 | "total_scheduled": 16287
207 | }, ...
208 | }
209 | },
210 | "queues": {
211 | "connectors": {
212 | "pending": 0
213 | }, ...
214 | }
215 | }
216 | ---------------
217 |
--------------------------------------------------------------------------------
/.buildkite/run-elasticsearch.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | #
3 | # Launch one or more Elasticsearch nodes via the Docker image,
4 | # to form a cluster suitable for running the REST API tests.
5 | #
6 | # Export the STACK_VERSION variable, eg. '8.0.0-SNAPSHOT'.
7 | # Export the TEST_SUITE variable, eg. 'free' or 'platinum' defaults to 'free'.
8 | # Export the NUMBER_OF_NODES variable to start more than 1 node
9 |
10 | # Version 1.6.1
11 | # - Initial version of the run-elasticsearch.sh script
12 | # - Deleting the volume should not dependent on the container still running
13 | # - Fixed `ES_JAVA_OPTS` config
14 | # - Moved to STACK_VERSION and TEST_VERSION
15 | # - Refactored into functions and imports
16 | # - Support NUMBER_OF_NODES
17 | # - Added 5 retries on docker pull for fixing transient network errors
18 | # - Added flags to make local CCR configurations work
19 | # - Added action.destructive_requires_name=false as the default will be true in v8
20 | # - Added ingest.geoip.downloader.enabled=false as it causes false positives in testing
21 | # - Moved ELASTIC_PASSWORD and xpack.security.enabled to the base arguments for "Security On by default"
22 | # - Use https only when TEST_SUITE is "platinum", when "free" use http
23 | # - Set xpack.security.enabled=false for "free" and xpack.security.enabled=true for "platinum"
24 |
25 | script_path=$(dirname $(realpath -s $0))
26 | source $script_path/functions/imports.sh
27 | set -euo pipefail
28 |
29 | echo -e "\033[34;1mINFO:\033[0m Take down node if called twice with the same arguments (DETACH=true) or on separate terminals \033[0m"
30 | cleanup_node $es_node_name
31 |
32 | master_node_name=${es_node_name}
33 | cluster_name=${moniker}${suffix}
34 |
35 | if [[ "${BUILDKITE:-}" == "true" ]]; then
36 | sudo sysctl -w vm.max_map_count=262144
37 | fi
38 |
39 | declare -a volumes
40 | environment=($(cat <<-END
41 | --env ELASTIC_PASSWORD=$elastic_password
42 | --env node.name=$es_node_name
43 | --env cluster.name=$cluster_name
44 | --env cluster.initial_master_nodes=$master_node_name
45 | --env discovery.seed_hosts=$master_node_name
46 | --env cluster.routing.allocation.disk.threshold_enabled=false
47 | --env bootstrap.memory_lock=true
48 | --env node.attr.testattr=test
49 | --env path.repo=/tmp
50 | --env repositories.url.allowed_urls=http://snapshot.test*
51 | --env action.destructive_requires_name=false
52 | --env ingest.geoip.downloader.enabled=false
53 | --env cluster.deprecation_indexing.enabled=false
54 | END
55 | ))
56 | if [[ "$TEST_SUITE" == "platinum" ]]; then
57 | environment+=($(cat <<-END
58 | --env xpack.security.enabled=true
59 | --env xpack.license.self_generated.type=trial
60 | --env xpack.security.http.ssl.enabled=true
61 | --env xpack.security.http.ssl.verification_mode=certificate
62 | --env xpack.security.http.ssl.key=certs/testnode.key
63 | --env xpack.security.http.ssl.certificate=certs/testnode.crt
64 | --env xpack.security.http.ssl.certificate_authorities=certs/ca.crt
65 | --env xpack.security.transport.ssl.enabled=true
66 | --env xpack.security.transport.ssl.verification_mode=certificate
67 | --env xpack.security.transport.ssl.key=certs/testnode.key
68 | --env xpack.security.transport.ssl.certificate=certs/testnode.crt
69 | --env xpack.security.transport.ssl.certificate_authorities=certs/ca.crt
70 | END
71 | ))
72 | volumes+=($(cat <<-END
73 | --volume $ssl_cert:/usr/share/elasticsearch/config/certs/testnode.crt
74 | --volume $ssl_key:/usr/share/elasticsearch/config/certs/testnode.key
75 | --volume $ssl_ca:/usr/share/elasticsearch/config/certs/ca.crt
76 | END
77 | ))
78 | else
79 | environment+=($(cat <<-END
80 | --env xpack.security.enabled=false
81 | --env xpack.security.http.ssl.enabled=false
82 | END
83 | ))
84 | fi
85 |
86 | cert_validation_flags=""
87 | if [[ "$TEST_SUITE" == "platinum" ]]; then
88 | cert_validation_flags="--insecure --cacert /usr/share/elasticsearch/config/certs/ca.crt --resolve ${es_node_name}:9200:127.0.0.1"
89 | fi
90 |
91 | # Pull the container, retry on failures up to 5 times with
92 | # short delays between each attempt. Fixes most transient network errors.
93 | docker_pull_attempts=0
94 | until [ "$docker_pull_attempts" -ge 5 ]
95 | do
96 | docker pull docker.elastic.co/elasticsearch/"$elasticsearch_container" && break
97 | docker_pull_attempts=$((docker_pull_attempts+1))
98 | echo "Failed to pull image, retrying in 10 seconds (retry $docker_pull_attempts/5)..."
99 | sleep 10
100 | done
101 |
102 | NUMBER_OF_NODES=${NUMBER_OF_NODES-1}
103 | http_port=9200
104 | for (( i=0; i<$NUMBER_OF_NODES; i++, http_port++ )); do
105 | node_name=${es_node_name}$i
106 | node_url=${external_elasticsearch_url/9200/${http_port}}$i
107 | if [[ "$i" == "0" ]]; then node_name=$es_node_name; fi
108 | environment+=($(cat <<-END
109 | --env node.name=$node_name
110 | END
111 | ))
112 | echo "$i: $http_port $node_url "
113 | volume_name=${node_name}-${suffix}-data
114 | volumes+=($(cat <<-END
115 | --volume $volume_name:/usr/share/elasticsearch/data${i}
116 | END
117 | ))
118 |
119 | # make sure we detach for all but the last node if DETACH=false (default) so all nodes are started
120 | local_detach="true"
121 | if [[ "$i" == "$((NUMBER_OF_NODES-1))" ]]; then local_detach=$DETACH; fi
122 | echo -e "\033[34;1mINFO:\033[0m Starting container $node_name \033[0m"
123 | set -x
124 | docker run \
125 | --name "$node_name" \
126 | --network "$network_name" \
127 | --env "ES_JAVA_OPTS=-Xms1g -Xmx1g -da:org.elasticsearch.xpack.ccr.index.engine.FollowingEngineAssertions" \
128 | "${environment[@]}" \
129 | "${volumes[@]}" \
130 | --publish "$http_port":9200 \
131 | --ulimit nofile=65536:65536 \
132 | --ulimit memlock=-1:-1 \
133 | --detach="$local_detach" \
134 | --health-cmd="curl $cert_validation_flags --fail $elasticsearch_url/_cluster/health || exit 1" \
135 | --health-interval=2s \
136 | --health-retries=20 \
137 | --health-timeout=2s \
138 | --rm \
139 | docker.elastic.co/elasticsearch/"$elasticsearch_container";
140 |
141 | set +x
142 | if wait_for_container "$es_node_name" "$network_name"; then
143 | echo -e "\033[32;1mSUCCESS:\033[0m Running on: $node_url\033[0m"
144 | fi
145 |
146 | done
147 |
148 |
--------------------------------------------------------------------------------
/utils/build-dists.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | """A command line tool for building and verifying releases."""
19 |
20 | import contextlib
21 | import os
22 | import re
23 | import shlex
24 | import shutil
25 | import sys
26 | import tempfile
27 |
28 | base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
29 | tmp_dir = None
30 |
31 |
32 | @contextlib.contextmanager
33 | def set_tmp_dir():
34 | global tmp_dir
35 | tmp_dir = tempfile.mkdtemp()
36 | yield tmp_dir
37 | shutil.rmtree(tmp_dir)
38 | tmp_dir = None
39 |
40 |
41 | def run(*argv, expect_exit_code=0):
42 | global tmp_dir
43 | if tmp_dir is None:
44 | os.chdir(base_dir)
45 | else:
46 | os.chdir(tmp_dir)
47 |
48 | cmd = " ".join(shlex.quote(x) for x in argv)
49 | print("$ " + cmd)
50 | exit_code = os.system(cmd)
51 | if exit_code != expect_exit_code:
52 | print(
53 | "Command exited incorrectly: should have been %d was %d"
54 | % (expect_exit_code, exit_code)
55 | )
56 | exit(exit_code or 1)
57 |
58 |
59 | def test_dist(dist):
60 | with set_tmp_dir() as tmp_dir:
61 | # Build the venv and install the dist
62 | run("python", "-m", "venv", os.path.join(tmp_dir, "venv"))
63 | venv_python = os.path.join(tmp_dir, "venv/bin/python")
64 | run(venv_python, "-m", "pip", "install", "-U", "pip", "mypy")
65 | run(venv_python, "-m", "pip", "install", dist)
66 |
67 | # Test the namespace and top-level clients
68 | run(venv_python, "-c", "import elastic_enterprise_search")
69 | for client in ("EnterpriseSearch", "AppSearch", "WorkplaceSearch"):
70 | run(venv_python, "-c", f"from elastic_enterprise_search import {client}")
71 |
72 | # Uninstall and ensure that clients aren't available
73 | run(venv_python, "-m", "pip", "uninstall", "--yes", "elastic-enterprise-search")
74 |
75 | run(venv_python, "-c", "import elastic_enterprise_search", expect_exit_code=256)
76 | for client in ("EnterpriseSearch", "AppSearch", "WorkplaceSearch"):
77 | run(
78 | venv_python,
79 | "-c",
80 | f"from elastic_enterprise_search import {client}",
81 | expect_exit_code=256,
82 | )
83 |
84 |
85 | def main():
86 | run("rm", "-rf", "build/", "dist/*", "*.egg-info", ".eggs")
87 |
88 | # Grab the major version to be used as a suffix.
89 | version_path = os.path.join(base_dir, "elastic_enterprise_search/_version.py")
90 | with open(version_path) as f:
91 | version = re.search(
92 | r"^__version__\s+=\s+[\"\']([^\"\']+)[\"\']", f.read(), re.M
93 | ).group(1)
94 |
95 | # If we're handed a version from the build manager we
96 | # should check that the version is correct or write
97 | # a new one.
98 | if len(sys.argv) >= 2:
99 | # 'build_version' is what the release manager wants,
100 | # 'expect_version' is what we're expecting to compare
101 | # the package version to before building the dists.
102 | build_version = expect_version = sys.argv[1]
103 |
104 | # '-SNAPSHOT' means we're making a pre-release.
105 | if "-SNAPSHOT" in build_version:
106 | # If there's no +dev already (as is the case on dev
107 | # branches like 7.x, master) then we need to add one.
108 | if not version.endswith("+dev"):
109 | version = version + "+dev"
110 | expect_version = expect_version.replace("-SNAPSHOT", "")
111 | if expect_version.endswith(".x"):
112 | expect_version = expect_version[:-2]
113 |
114 | # For snapshots we ensure that the version in the package
115 | # at least *starts* with the version. This is to support
116 | # build_version='7.x-SNAPSHOT'.
117 | if not version.startswith(expect_version):
118 | print(
119 | "Version of package (%s) didn't match the "
120 | "expected release version (%s)" % (version, build_version)
121 | )
122 | exit(1)
123 |
124 | # A release that will be tagged, we want
125 | # there to be no '+dev', etc.
126 | elif expect_version != version:
127 | print(
128 | "Version of package (%s) didn't match the "
129 | "expected release version (%s)" % (version, build_version)
130 | )
131 | exit(1)
132 |
133 | # Ensure that the version within 'elasticsearch/_version.py' is correct.
134 | with open(version_path) as f:
135 | version_data = f.read()
136 | version_data = re.sub(
137 | r"__version__ = \"[^\"]+\"",
138 | f'__version__ = "{version}"',
139 | version_data,
140 | )
141 | with open(version_path, "w") as f:
142 | f.truncate()
143 | f.write(version_data)
144 |
145 | # Build the sdist/wheels
146 | run("python", "-m", "build")
147 |
148 | # Test everything that got created
149 | dists = os.listdir(os.path.join(base_dir, "dist"))
150 | assert len(dists) == 2
151 | for dist in dists:
152 | test_dist(os.path.join(base_dir, "dist", dist))
153 |
154 | run("git", "checkout", "--", "elastic_enterprise_search/")
155 |
156 | # After this run 'python -m twine upload dist/*'
157 | print(
158 | "\n\n"
159 | "===============================\n\n"
160 | " * Releases are ready! *\n\n"
161 | "$ python -m twine upload dist/*\n\n"
162 | "==============================="
163 | )
164 |
165 |
166 | if __name__ == "__main__":
167 | main()
168 |
--------------------------------------------------------------------------------
/elastic_enterprise_search/_sync/client/enterprise_search.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import typing as t
19 |
20 | from elastic_transport import ObjectApiResponse
21 |
22 | from ..._utils import _quote_query_form, _rewrite_parameters
23 | from ._base import BaseClient
24 |
25 |
26 | class EnterpriseSearch(BaseClient):
27 | # AUTO-GENERATED-API-DEFINITIONS #
28 |
29 | @_rewrite_parameters()
30 | def get_health(
31 | self,
32 | ) -> ObjectApiResponse[t.Any]:
33 | """
34 | Get information on the health of a deployment and basic statistics around resource
35 | usage
36 |
37 | ``_
38 | """
39 | __headers = {"accept": "application/json"}
40 | return self.perform_request( # type: ignore[return-value]
41 | "GET", "/api/ent/v1/internal/health", headers=__headers
42 | )
43 |
44 | @_rewrite_parameters()
45 | def get_read_only(
46 | self,
47 | ) -> ObjectApiResponse[t.Any]:
48 | """
49 | Get the read-only flag's state
50 |
51 | ``_
52 | """
53 | __headers = {"accept": "application/json"}
54 | return self.perform_request( # type: ignore[return-value]
55 | "GET", "/api/ent/v1/internal/read_only_mode", headers=__headers
56 | )
57 |
58 | @_rewrite_parameters(
59 | body_fields=True,
60 | )
61 | def put_read_only(
62 | self,
63 | *,
64 | enabled: bool,
65 | ) -> ObjectApiResponse[t.Any]:
66 | """
67 | Update the read-only flag's state
68 |
69 | ``_
70 |
71 | :param enabled:
72 | """
73 | if enabled is None:
74 | raise ValueError("Empty value passed for parameter 'enabled'")
75 | __body: t.Dict[str, t.Any] = {}
76 | if enabled is not None:
77 | __body["enabled"] = enabled
78 | __headers = {"accept": "application/json", "content-type": "application/json"}
79 | return self.perform_request( # type: ignore[return-value]
80 | "PUT", "/api/ent/v1/internal/read_only_mode", body=__body, headers=__headers
81 | )
82 |
83 | @_rewrite_parameters()
84 | def get_stats(
85 | self,
86 | *,
87 | include: t.Optional[t.Union[t.List[str], t.Tuple[str, ...]]] = None,
88 | ) -> ObjectApiResponse[t.Any]:
89 | """
90 | Get information about the resource usage of the application, the state of different
91 | internal queues, etc.
92 |
93 | ``_
94 |
95 | :param include: Comma-separated list of stats to return
96 | """
97 | __query: t.Dict[str, t.Any] = {}
98 | if include is not None:
99 | __query["include"] = _quote_query_form("include", include)
100 | __headers = {"accept": "application/json"}
101 | return self.perform_request( # type: ignore[return-value]
102 | "GET", "/api/ent/v1/internal/stats", params=__query, headers=__headers
103 | )
104 |
105 | @_rewrite_parameters()
106 | def get_storage(
107 | self,
108 | ) -> ObjectApiResponse[t.Any]:
109 | """
110 | Get information on the application indices and the space used
111 |
112 | ``_
113 | """
114 | __headers = {"accept": "application/json"}
115 | return self.perform_request( # type: ignore[return-value]
116 | "GET", "/api/ent/v1/internal/storage", headers=__headers
117 | )
118 |
119 | @_rewrite_parameters()
120 | def get_stale_storage(
121 | self,
122 | ) -> ObjectApiResponse[t.Any]:
123 | """
124 | Get information on the outdated application indices
125 |
126 | ``_
127 | """
128 | __headers = {"accept": "application/json"}
129 | return self.perform_request( # type: ignore[return-value]
130 | "GET", "/api/ent/v1/internal/storage/stale", headers=__headers
131 | )
132 |
133 | @_rewrite_parameters()
134 | def delete_stale_storage(
135 | self,
136 | *,
137 | force: t.Optional[bool] = None,
138 | ) -> ObjectApiResponse[t.Any]:
139 | """
140 | Cleanup outdated application indices
141 |
142 | ``_
143 |
144 | :param force: The value for the "force" flag
145 | """
146 | __query: t.Dict[str, t.Any] = {}
147 | if force is not None:
148 | __query["force"] = force
149 | __headers = {"accept": "application/json"}
150 | return self.perform_request( # type: ignore[return-value]
151 | "DELETE",
152 | "/api/ent/v1/internal/storage/stale",
153 | params=__query,
154 | headers=__headers,
155 | )
156 |
157 | @_rewrite_parameters()
158 | def get_version(
159 | self,
160 | ) -> ObjectApiResponse[t.Any]:
161 | """
162 | Get version information for this server
163 |
164 | ``_
165 | """
166 | __headers = {"accept": "application/json"}
167 | return self.perform_request( # type: ignore[return-value]
168 | "GET", "/api/ent/v1/internal/version", headers=__headers
169 | )
170 |
171 | @_rewrite_parameters()
172 | def get_search_engines(
173 | self,
174 | ) -> ObjectApiResponse[t.Any]:
175 | """
176 | Retrieve information about search engines
177 |
178 | ``_
179 | """
180 | __headers = {"accept": "application/json"}
181 | return self.perform_request( # type: ignore[return-value]
182 | "GET", "/api/search_engines", headers=__headers
183 | )
184 |
--------------------------------------------------------------------------------
/elastic_enterprise_search/_async/client/enterprise_search.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import typing as t
19 |
20 | from elastic_transport import ObjectApiResponse
21 |
22 | from ..._utils import _quote_query_form, _rewrite_parameters
23 | from ._base import BaseClient
24 |
25 |
26 | class AsyncEnterpriseSearch(BaseClient):
27 | # AUTO-GENERATED-API-DEFINITIONS #
28 |
29 | @_rewrite_parameters()
30 | async def get_health(
31 | self,
32 | ) -> ObjectApiResponse[t.Any]:
33 | """
34 | Get information on the health of a deployment and basic statistics around resource
35 | usage
36 |
37 | ``_
38 | """
39 | __headers = {"accept": "application/json"}
40 | return await self.perform_request( # type: ignore[return-value]
41 | "GET", "/api/ent/v1/internal/health", headers=__headers
42 | )
43 |
44 | @_rewrite_parameters()
45 | async def get_read_only(
46 | self,
47 | ) -> ObjectApiResponse[t.Any]:
48 | """
49 | Get the read-only flag's state
50 |
51 | ``_
52 | """
53 | __headers = {"accept": "application/json"}
54 | return await self.perform_request( # type: ignore[return-value]
55 | "GET", "/api/ent/v1/internal/read_only_mode", headers=__headers
56 | )
57 |
58 | @_rewrite_parameters(
59 | body_fields=True,
60 | )
61 | async def put_read_only(
62 | self,
63 | *,
64 | enabled: bool,
65 | ) -> ObjectApiResponse[t.Any]:
66 | """
67 | Update the read-only flag's state
68 |
69 | ``_
70 |
71 | :param enabled:
72 | """
73 | if enabled is None:
74 | raise ValueError("Empty value passed for parameter 'enabled'")
75 | __body: t.Dict[str, t.Any] = {}
76 | if enabled is not None:
77 | __body["enabled"] = enabled
78 | __headers = {"accept": "application/json", "content-type": "application/json"}
79 | return await self.perform_request( # type: ignore[return-value]
80 | "PUT", "/api/ent/v1/internal/read_only_mode", body=__body, headers=__headers
81 | )
82 |
83 | @_rewrite_parameters()
84 | async def get_stats(
85 | self,
86 | *,
87 | include: t.Optional[t.Union[t.List[str], t.Tuple[str, ...]]] = None,
88 | ) -> ObjectApiResponse[t.Any]:
89 | """
90 | Get information about the resource usage of the application, the state of different
91 | internal queues, etc.
92 |
93 | ``_
94 |
95 | :param include: Comma-separated list of stats to return
96 | """
97 | __query: t.Dict[str, t.Any] = {}
98 | if include is not None:
99 | __query["include"] = _quote_query_form("include", include)
100 | __headers = {"accept": "application/json"}
101 | return await self.perform_request( # type: ignore[return-value]
102 | "GET", "/api/ent/v1/internal/stats", params=__query, headers=__headers
103 | )
104 |
105 | @_rewrite_parameters()
106 | async def get_storage(
107 | self,
108 | ) -> ObjectApiResponse[t.Any]:
109 | """
110 | Get information on the application indices and the space used
111 |
112 | ``_
113 | """
114 | __headers = {"accept": "application/json"}
115 | return await self.perform_request( # type: ignore[return-value]
116 | "GET", "/api/ent/v1/internal/storage", headers=__headers
117 | )
118 |
119 | @_rewrite_parameters()
120 | async def get_stale_storage(
121 | self,
122 | ) -> ObjectApiResponse[t.Any]:
123 | """
124 | Get information on the outdated application indices
125 |
126 | ``_
127 | """
128 | __headers = {"accept": "application/json"}
129 | return await self.perform_request( # type: ignore[return-value]
130 | "GET", "/api/ent/v1/internal/storage/stale", headers=__headers
131 | )
132 |
133 | @_rewrite_parameters()
134 | async def delete_stale_storage(
135 | self,
136 | *,
137 | force: t.Optional[bool] = None,
138 | ) -> ObjectApiResponse[t.Any]:
139 | """
140 | Cleanup outdated application indices
141 |
142 | ``_
143 |
144 | :param force: The value for the "force" flag
145 | """
146 | __query: t.Dict[str, t.Any] = {}
147 | if force is not None:
148 | __query["force"] = force
149 | __headers = {"accept": "application/json"}
150 | return await self.perform_request( # type: ignore[return-value]
151 | "DELETE",
152 | "/api/ent/v1/internal/storage/stale",
153 | params=__query,
154 | headers=__headers,
155 | )
156 |
157 | @_rewrite_parameters()
158 | async def get_version(
159 | self,
160 | ) -> ObjectApiResponse[t.Any]:
161 | """
162 | Get version information for this server
163 |
164 | ``_
165 | """
166 | __headers = {"accept": "application/json"}
167 | return await self.perform_request( # type: ignore[return-value]
168 | "GET", "/api/ent/v1/internal/version", headers=__headers
169 | )
170 |
171 | @_rewrite_parameters()
172 | async def get_search_engines(
173 | self,
174 | ) -> ObjectApiResponse[t.Any]:
175 | """
176 | Retrieve information about search engines
177 |
178 | ``_
179 | """
180 | __headers = {"accept": "application/json"}
181 | return await self.perform_request( # type: ignore[return-value]
182 | "GET", "/api/search_engines", headers=__headers
183 | )
184 |
--------------------------------------------------------------------------------
/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | # Licensed to Elasticsearch B.V. under one or more contributor
2 | # license agreements. See the NOTICE file distributed with
3 | # this work for additional information regarding copyright
4 | # ownership. Elasticsearch B.V. licenses this file to you under
5 | # the Apache License, Version 2.0 (the "License"); you may
6 | # not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # http://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing,
12 | # software distributed under the License is distributed on an
13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 | # KIND, either express or implied. See the License for the
15 | # specific language governing permissions and limitations
16 | # under the License.
17 |
18 | import datetime
19 | import warnings
20 |
21 | import pytest
22 | from dateutil import tz
23 |
24 | from elastic_enterprise_search import _utils
25 |
26 |
27 | def test_format_datetime_tz_naive():
28 | dt = datetime.datetime.now()
29 | assert dt.tzinfo is None
30 |
31 | # Should serialize the same as local timezone
32 | dt2 = dt.replace(tzinfo=tz.tzlocal())
33 |
34 | assert _utils.format_datetime(dt) == _utils.format_datetime(dt2)
35 |
36 | # This is the dicey one, utcnow() is very broken and not recommended.
37 | dt = datetime.datetime.utcnow()
38 | assert dt.tzinfo is None
39 |
40 | dt2 = datetime.datetime.now(tz=tz.UTC)
41 |
42 | # The two are only equal if the local timezone is UTC
43 | # otherwise they are different :(
44 | if tz.tzlocal() == tz.UTC:
45 | assert _utils.format_datetime(dt) == _utils.format_datetime(dt2)
46 | else:
47 | assert _utils.format_datetime(dt) != _utils.format_datetime(dt2)
48 |
49 |
50 | def test_to_params():
51 | params = [
52 | ("a", 1),
53 | ("b", "z"),
54 | ("c", ["d", 2]),
55 | ("e", datetime.date(year=2020, month=1, day=1)),
56 | (
57 | "f",
58 | datetime.datetime(
59 | year=2020,
60 | month=2,
61 | day=3,
62 | hour=4,
63 | minute=5,
64 | second=6,
65 | microsecond=7,
66 | tzinfo=tz.gettz("HST"),
67 | ),
68 | ),
69 | ("g", (True, False)),
70 | ("h", b"hello-world"),
71 | ("i", None),
72 | ("z", "[]1234567890-_~. `=!@#$%^&*()+;'{}:,<>?/\\\""),
73 | ("kv", {"key": [1, "2", {"k": "v"}]}),
74 | ]
75 | assert _utils._quote_query(params) == (
76 | "a=1&"
77 | "b=z&"
78 | "c[]=d&"
79 | "c[]=2&"
80 | "e=2020-01-01&"
81 | "f=2020-02-03T04:05:06.000007-10:00&"
82 | "g[]=True&g[]=False&"
83 | "h=hello-world&"
84 | "i=None&"
85 | "z=[]1234567890-_~.%20%60%3D%21%40%23%24%25%5E%26*%28%29%2B%3B%27%7B%7D:,%3C%3E%3F%2F%5C%22&"
86 | "kv[key][]=1&kv[key][]=2&kv[key][][k]=v"
87 | )
88 |
89 |
90 | @pytest.mark.parametrize(
91 | ["value", "dt"],
92 | [
93 | (
94 | "2020-01-02T03:04:05Z",
95 | datetime.datetime(
96 | year=2020, month=1, day=2, hour=3, minute=4, second=5, tzinfo=tz.UTC
97 | ),
98 | ),
99 | (
100 | "2020-01-02T11:12:59+00:00",
101 | datetime.datetime(
102 | year=2020, month=1, day=2, hour=11, minute=12, second=59, tzinfo=tz.UTC
103 | ),
104 | ),
105 | (
106 | # An odd case of '-00:00' but we handle it anyways.
107 | "2020-01-02T11:12:59-00:00",
108 | datetime.datetime(
109 | year=2020, month=1, day=2, hour=11, minute=12, second=59, tzinfo=tz.UTC
110 | ),
111 | ),
112 | (
113 | "2020-01-02 11:12:59-10:00",
114 | datetime.datetime(
115 | year=2020,
116 | month=1,
117 | day=2,
118 | hour=11,
119 | minute=12,
120 | second=59,
121 | tzinfo=tz.gettz("HST"),
122 | ),
123 | ),
124 | (
125 | # 'Asia/Kolkata' is Indian Standard Time which is UTC+5:30 and doesn't have DST
126 | "2020-01-02T11:12:59+05:30",
127 | datetime.datetime(
128 | year=2020,
129 | month=1,
130 | day=2,
131 | hour=11,
132 | minute=12,
133 | second=59,
134 | tzinfo=tz.gettz("Asia/Kolkata"),
135 | ),
136 | ),
137 | ],
138 | )
139 | def test_parse_datetime(value, dt):
140 | assert _utils.parse_datetime(value) == dt
141 |
142 |
143 | def test_parse_datetime_bad_format():
144 | with pytest.raises(ValueError) as e:
145 | _utils.parse_datetime("2020-03-10T10:10:10")
146 | assert (
147 | str(e.value)
148 | == "Datetime must match format '(YYYY)-(MM)-(DD)T(HH):(MM):(SS)(TZ)' was '2020-03-10T10:10:10'"
149 | )
150 |
151 |
152 | class Client:
153 | def __init__(self):
154 | self.options_kwargs = []
155 |
156 | def options(self, **kwargs):
157 | self.options_kwargs.append(kwargs)
158 | return self
159 |
160 | @_utils._rewrite_parameters(body_name="documents")
161 | def func_body_name(self, *args, **kwargs):
162 | return (args, kwargs)
163 |
164 | @_utils._rewrite_parameters(body_fields=True)
165 | def func_body_fields(self, *args, **kwargs):
166 | return (args, kwargs)
167 |
168 |
169 | def test_rewrite_parameters_body_name():
170 | client = Client()
171 | with warnings.catch_warnings(record=True) as w:
172 | _, kwargs = client.func_body_name(body=[1, 2, 3])
173 | assert kwargs == {"documents": [1, 2, 3]}
174 | assert (
175 | len(w) == 1
176 | and str(w[0].message)
177 | == "The 'body' parameter is deprecated and will be removed in a future version. Instead use the 'documents' parameter."
178 | )
179 |
180 | with warnings.catch_warnings(record=True) as w:
181 | _, kwargs = client.func_body_fields(documents=[1, 2, 3])
182 | assert kwargs == {"documents": [1, 2, 3]}
183 | assert w == []
184 |
185 |
186 | def test_rewrite_parameters_body_field():
187 | client = Client()
188 | with warnings.catch_warnings(record=True) as w:
189 | _, kwargs = client.func_body_fields(params={"param": 1}, body={"field": 2})
190 | assert kwargs == {"param": 1, "field": 2}
191 | assert len(w) == 2 and {str(wn.message) for wn in w} == {
192 | "The 'params' parameter is deprecated and will be removed in a future version. Instead use individual parameters.",
193 | "The 'body' parameter is deprecated and will be removed in a future version. Instead use individual parameters.",
194 | }
195 |
196 |
197 | @pytest.mark.parametrize(
198 | ["http_auth", "auth_kwargs"],
199 | [
200 | ("api-key", {"bearer_auth": "api-key"}),
201 | (("username", "password"), {"basic_auth": ("username", "password")}),
202 | (["username", "password"], {"basic_auth": ["username", "password"]}),
203 | ],
204 | )
205 | def test_rewrite_parameters_http_auth(http_auth, auth_kwargs):
206 | client = Client()
207 | with warnings.catch_warnings(record=True) as w:
208 | args, kwargs = client.func_body_fields(http_auth=http_auth)
209 | assert args == ()
210 | assert kwargs == {}
211 | assert client.options_kwargs == [auth_kwargs]
212 | assert len(w) == 1 and {str(wn.message) for wn in w} == {
213 | "Passing transport options in the API method is deprecated. Use 'Client.options()' instead."
214 | }
215 |
216 |
217 | @pytest.mark.parametrize(
218 | ["body", "page_kwargs"],
219 | [
220 | ({"page": {"current": 1}}, {"current_page": 1}),
221 | ({"page": {"size": 1}}, {"page_size": 1}),
222 | ({"page": {"current": 1, "size": 2}}, {"current_page": 1, "page_size": 2}),
223 | ],
224 | )
225 | def test_rewrite_parameters_pagination(body, page_kwargs):
226 | client = Client()
227 | _, kwargs = client.func_body_fields(body=body)
228 | assert kwargs == page_kwargs
229 |
230 |
231 | def test_rewrite_parameters_bad_body():
232 | client = Client()
233 | with pytest.raises(ValueError) as e:
234 | client.func_body_fields(body=[1, 2, 3])
235 | assert str(e.value) == (
236 | "Couldn't merge 'body' with other parameters as it wasn't a mapping. "
237 | "Instead of using 'body' use individual API parameters"
238 | )
239 |
--------------------------------------------------------------------------------