├── .buildkite ├── Dockerfile ├── certs │ ├── README.md │ ├── ca.crt │ ├── ca.key │ ├── testnode.crt │ ├── testnode.key │ ├── testnode_no_san.crt │ └── testnode_no_san.key ├── functions │ ├── cleanup.sh │ ├── imports.sh │ └── wait-for-container.sh ├── pipeline.yml ├── pull-requests.json ├── run-elasticsearch.sh ├── run-enterprise-search.sh ├── run-repository.sh └── run-tests ├── .ci ├── .gitignore └── make.sh ├── .dockerignore ├── .github └── workflows │ ├── backport.yml │ ├── ci.yml │ └── unified-release.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── assets └── elastic-enterprise-search-logo.png ├── catalog-info.yaml ├── dev-requirements.txt ├── docs └── guide │ ├── app-search-api.asciidoc │ ├── connecting.asciidoc │ ├── enterprise-search-api.asciidoc │ ├── index.asciidoc │ ├── installation.asciidoc │ ├── overview.asciidoc │ ├── release-notes │ ├── 7-10-0.asciidoc │ ├── 7-11-0.asciidoc │ ├── 7-12-0.asciidoc │ ├── 7-13-0.asciidoc │ ├── 7-14-0.asciidoc │ ├── 7-15-0.asciidoc │ ├── 7-16-0.asciidoc │ ├── 7-17-0.asciidoc │ ├── 8-10-0.asciidoc │ ├── 8-11-0.asciidoc │ ├── 8-18-0.asciidoc │ ├── 8-2-0.asciidoc │ ├── 8-3-0.asciidoc │ ├── 8-4-0.asciidoc │ └── index.asciidoc │ └── workplace-search-api.asciidoc ├── elastic_enterprise_search ├── __init__.py ├── _async │ ├── __init__.py │ └── client │ │ ├── __init__.py │ │ ├── _base.py │ │ ├── app_search.py │ │ ├── enterprise_search.py │ │ └── workplace_search.py ├── _serializer.py ├── _sync │ ├── __init__.py │ └── client │ │ ├── __init__.py │ │ ├── _base.py │ │ ├── app_search.py │ │ ├── enterprise_search.py │ │ └── workplace_search.py ├── _utils.py ├── _version.py └── exceptions.py ├── noxfile.py ├── setup.cfg ├── setup.py ├── tests ├── __init__.py ├── client │ ├── app_search │ │ ├── __init__.py │ │ ├── cassettes │ │ │ ├── test_delete_documents.yaml │ │ │ ├── test_index_documents.yaml │ │ │ ├── test_list_documents.yaml │ │ │ ├── test_list_engines.yaml │ │ │ ├── test_meta_engine.yaml │ │ │ ├── test_not_authorized.yaml │ │ │ ├── test_query_suggestions.yaml │ │ │ ├── test_search.yaml │ │ │ └── test_search_es_search.yaml │ │ ├── conftest.py │ │ ├── test_app_search.py │ │ └── test_search_es_search.py │ ├── enterprise_search │ │ ├── __init__.py │ │ ├── cassettes │ │ │ ├── test_get_and_put_read_only.yaml │ │ │ ├── test_get_health.yaml │ │ │ ├── test_get_stats.yaml │ │ │ └── test_get_version.yaml │ │ └── test_enterprise_search.py │ ├── test_auth.py │ ├── test_client_meta.py │ ├── test_enterprise_search.py │ ├── test_options.py │ └── workplace_search │ │ ├── __init__.py │ │ ├── cassettes │ │ ├── test_index_documents.yaml │ │ ├── test_index_documents_content_source_not_found.yaml │ │ ├── test_oauth_exchange_for_access_token_code.yaml │ │ ├── test_oauth_exchange_for_access_token_invalid_grant.yaml │ │ └── test_oauth_exchange_for_access_token_refresh_token.yaml │ │ └── test_workplace_search.py ├── conftest.py ├── server │ ├── __init__.py │ ├── async_ │ │ ├── __init__.py │ │ ├── conftest.py │ │ └── test_app_search.py │ ├── conftest.py │ ├── test_app_search.py │ ├── test_enterprise_search.py │ └── test_httpbin.py ├── test_oauth.py ├── test_package.py ├── test_serializer.py ├── test_utils.py └── utils.py └── utils ├── build-dists.py ├── bump-version.py ├── license-headers.py └── run-unasync.py /.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 | -------------------------------------------------------------------------------- /.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/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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.ci/.gitignore: -------------------------------------------------------------------------------- 1 | tmp/ 2 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | docs 2 | example 3 | venv 4 | .git 5 | .tox 6 | .nox 7 | .*_cache 8 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | Elastic Enterprise Search 16 | 17 |

18 |

19 | PyPI Version 20 | Supported Python Versions 21 | Downloads 22 | Buildkite Status 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 | -------------------------------------------------------------------------------- /assets/elastic-enterprise-search-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/elastic/enterprise-search-python/9c5345eceae73cffa3a9ed43e9d6334395a65c02/assets/elastic-enterprise-search-logo.png -------------------------------------------------------------------------------- /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 | schedules: 42 | main_semi_daily: 43 | branch: 'main' 44 | cronline: '@daily' 45 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | build 2 | nox 3 | twine 4 | unasync 5 | aiohttp 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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-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/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`. -------------------------------------------------------------------------------- /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-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 | ==== -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [isort] 2 | profile = black 3 | -------------------------------------------------------------------------------- /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/__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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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 | -------------------------------------------------------------------------------- /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_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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------