diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 77f11bc8..a69e7183 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -50,3 +50,19 @@ jobs: run: pipenv install --dev - name: Linter with pylint run: pipenv run pylint meilisearch + + mypy: + name: mypy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Set up Python 3.9 + uses: actions/setup-python@v1 + with: + python-version: 3.9 + - name: Install pipenv + uses: dschep/install-pipenv-action@v1 + - name: Install dependencies + run: pipenv install --dev + - name: mypy type check + run: pipenv run mypy meilisearch diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a7c3010f..d9b27941 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,18 +34,20 @@ pipenv install --dev ### Tests and Linter -Each PR should pass the tests and the linter to be accepted. +Each PR should pass the tests, mypy type checking, and the linter to be accepted. ```bash # Tests docker pull getmeili/meilisearch:latest # Fetch the latest version of MeiliSearch image from Docker Hub docker run -p 7700:7700 getmeili/meilisearch:latest ./meilisearch --master-key=masterKey --no-analytics=true pipenv run pytest meilisearch +# MyPy +pipenv run mypy meilisearch # Linter pipenv run pylint meilisearch ``` -Optionally tox can be used to run test on all supported version of Python and linting. +Optionally tox can be used to run test on all supported version of Python, mypy, and linting. ```bash docker pull getmeili/meilisearch:latest # Fetch the latest version of MeiliSearch image from Docker Hub diff --git a/Pipfile b/Pipfile index 93cce396..e3d9dd8b 100644 --- a/Pipfile +++ b/Pipfile @@ -4,6 +4,7 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] +mypy = "*" pylint = "*" pytest = "*" pdoc3 = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 0bd70378..2704b0d4 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ce41d6ee6c13388760a1d3f32e9ac6790210f96ef4cc5d908706403496b584b7" + "sha256": "46fd4983b9734342ae70d92f9d37b985e3c6c459d804ad65c4052682d24142d2" }, "pipfile-spec": 6, "requires": {}, @@ -26,6 +26,7 @@ "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==4.0.0" }, "idna": { @@ -33,6 +34,7 @@ "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.10" }, "requests": { @@ -48,6 +50,7 @@ "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c", "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", "version": "==1.26.5" } }, @@ -64,6 +67,7 @@ "sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e", "sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975" ], + "markers": "python_version ~= '3.6'", "version": "==2.5.6" }, "attrs": { @@ -71,6 +75,7 @@ "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==21.2.0" }, "certifi": { @@ -106,6 +111,7 @@ "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6", "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d" ], + "markers": "python_version >= '3.6' and python_version < '4.0'", "version": "==5.8.0" }, "lazy-object-proxy": { @@ -133,6 +139,7 @@ "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93", "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", "version": "==1.6.0" }, "mako": { @@ -140,6 +147,7 @@ "sha256:17831f0b7087c313c0ffae2bcbbd3c1d5ba9eeac9c38f2eb7b50e8c99fe9d5ab", "sha256:aea166356da44b9b830c8023cd9b557fa856bd8b4035d6de771ca027dfc5cc6e" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.1.4" }, "markdown": { @@ -147,6 +155,7 @@ "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49", "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c" ], + "markers": "python_version >= '3.6'", "version": "==3.3.4" }, "markupsafe": { @@ -186,6 +195,7 @@ "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" ], + "markers": "python_version >= '3.6'", "version": "==2.0.1" }, "mccabe": { @@ -195,11 +205,47 @@ ], "version": "==0.6.1" }, + "mypy": { + "hashes": [ + "sha256:0d0a87c0e7e3a9becdfbe936c981d32e5ee0ccda3e0f07e1ef2c3d1a817cf73e", + "sha256:25adde9b862f8f9aac9d2d11971f226bd4c8fbaa89fb76bdadb267ef22d10064", + "sha256:28fb5479c494b1bab244620685e2eb3c3f988d71fd5d64cc753195e8ed53df7c", + "sha256:2f9b3407c58347a452fc0736861593e105139b905cca7d097e413453a1d650b4", + "sha256:33f159443db0829d16f0a8d83d94df3109bb6dd801975fe86bacb9bf71628e97", + "sha256:3f2aca7f68580dc2508289c729bd49ee929a436208d2b2b6aab15745a70a57df", + "sha256:499c798053cdebcaa916eef8cd733e5584b5909f789de856b482cd7d069bdad8", + "sha256:4eec37370483331d13514c3f55f446fc5248d6373e7029a29ecb7b7494851e7a", + "sha256:552a815579aa1e995f39fd05dde6cd378e191b063f031f2acfe73ce9fb7f9e56", + "sha256:5873888fff1c7cf5b71efbe80e0e73153fe9212fafdf8e44adfe4c20ec9f82d7", + "sha256:61a3d5b97955422964be6b3baf05ff2ce7f26f52c85dd88db11d5e03e146a3a6", + "sha256:674e822aa665b9fd75130c6c5f5ed9564a38c6cea6a6432ce47eafb68ee578c5", + "sha256:7ce3175801d0ae5fdfa79b4f0cfed08807af4d075b402b7e294e6aa72af9aa2a", + "sha256:9743c91088d396c1a5a3c9978354b61b0382b4e3c440ce83cf77994a43e8c521", + "sha256:9f94aac67a2045ec719ffe6111df543bac7874cee01f41928f6969756e030564", + "sha256:a26f8ec704e5a7423c8824d425086705e381b4f1dfdef6e3a1edab7ba174ec49", + "sha256:abf7e0c3cf117c44d9285cc6128856106183938c68fd4944763003decdcfeb66", + "sha256:b09669bcda124e83708f34a94606e01b614fa71931d356c1f1a5297ba11f110a", + "sha256:cd07039aa5df222037005b08fbbfd69b3ab0b0bd7a07d7906de75ae52c4e3119", + "sha256:d23e0ea196702d918b60c8288561e722bf437d82cb7ef2edcd98cfa38905d506", + "sha256:d65cc1df038ef55a99e617431f0553cd77763869eebdf9042403e16089fe746c", + "sha256:d7da2e1d5f558c37d6e8c1246f1aec1e7349e4913d8fb3cb289a35de573fe2eb" + ], + "index": "pypi", + "version": "==0.812" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, "packaging": { "hashes": [ "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==20.9" }, "pdoc3": { @@ -214,6 +260,7 @@ "sha256:05958fadcd70b2de6a27542fcd2bd72dd5c59c6d35307fdac3e06361fb06e30e", "sha256:d180f5be4775c552fd5e69ae18a9d6099d9dafb462efe54f11c72cb5f4d5e977" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2021.5.29" }, "pluggy": { @@ -221,6 +268,7 @@ "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.13.1" }, "py": { @@ -228,6 +276,7 @@ "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.10.0" }, "pylint": { @@ -243,15 +292,16 @@ "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.4.7" }, "pytest": { "hashes": [ - "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634", - "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc" + "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b", + "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890" ], "index": "pypi", - "version": "==6.2.3" + "version": "==6.2.4" }, "pytest-ordering": { "hashes": [ @@ -267,6 +317,7 @@ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.16.0" }, "toml": { @@ -274,15 +325,16 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.10.2" }, "tox": { "hashes": [ - "sha256:05a4dbd5e4d3d8269b72b55600f0b0303e2eb47ad5c6fe76d3576f4c58d93661", - "sha256:e007673f3595cede9b17a7c4962389e4305d4a3682a6c5a4159a1453b4f326aa" + "sha256:307a81ddb82bd463971a273f33e9533a24ed22185f27db8ce3386bff27d324e3", + "sha256:b0b5818049a1c1997599d42012a637a33f24c62ab8187223fdd318fa8522637b" ], "index": "pypi", - "version": "==3.23.0" + "version": "==3.23.1" }, "tox-pipenv": { "hashes": [ @@ -292,11 +344,55 @@ "index": "pypi", "version": "==1.10.1" }, + "typed-ast": { + "hashes": [ + "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace", + "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff", + "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266", + "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528", + "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6", + "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808", + "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4", + "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363", + "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341", + "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04", + "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41", + "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e", + "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3", + "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899", + "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805", + "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c", + "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c", + "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39", + "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a", + "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3", + "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7", + "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f", + "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075", + "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0", + "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40", + "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428", + "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927", + "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3", + "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f", + "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65" + ], + "version": "==1.4.3" + }, + "typing-extensions": { + "hashes": [ + "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", + "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", + "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" + ], + "version": "==3.10.0.0" + }, "virtualenv": { "hashes": [ "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467", "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==20.4.7" }, "virtualenv-clone": { @@ -304,6 +400,7 @@ "sha256:07e74418b7cc64f4fda987bf5bc71ebd59af27a7bc9e8a8ee9fd54b1f2390a27", "sha256:665e48dd54c84b98b71a657acb49104c54e7652bce9c1c4f6c6976ed4c827a29" ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==0.5.4" }, "wrapt": { diff --git a/bors.toml b/bors.toml index a9f7e596..96f571b3 100644 --- a/bors.toml +++ b/bors.toml @@ -1,5 +1,6 @@ status = [ 'pylint', + 'mypy', 'integration-tests (3.6)', 'integration-tests (3.7)', 'integration-tests (3.8)', diff --git a/meilisearch/_httprequests.py b/meilisearch/_httprequests.py index ae3ab1fa..865080f9 100644 --- a/meilisearch/_httprequests.py +++ b/meilisearch/_httprequests.py @@ -1,5 +1,7 @@ import json +from typing import Any, Callable, Dict, List, Optional, Union import requests +from meilisearch.config import Config from meilisearch.errors import ( MeiliSearchApiError, MeiliSearchCommunicationError, @@ -7,14 +9,19 @@ ) class HttpRequests: - def __init__(self, config): + def __init__(self, config: Config) -> None: self.config = config self.headers = { 'X-Meili-Api-Key': self.config.api_key, 'Content-Type': 'application/json' } - def send_request(self, http_method, path, body=None): + def send_request( + self, + http_method: Callable, + path: str, + body: Optional[Union[Dict[str, Any], List[Dict[str, Any]], List[str]]] = None, + ) -> Any: try: request_path = self.config.url + '/' + path request = http_method( @@ -26,32 +33,50 @@ def send_request(self, http_method, path, body=None): return self.__validate(request) except requests.exceptions.Timeout as err: - raise MeiliSearchTimeoutError(err) from err + raise MeiliSearchTimeoutError(str(err)) from err except requests.exceptions.ConnectionError as err: - raise MeiliSearchCommunicationError(err) from err + raise MeiliSearchCommunicationError(str(err)) from err - def get(self, path): + def get( + self, path: str + ) -> Any: return self.send_request(requests.get, path) - def post(self, path, body=None): + def post( + self, + path: str, + body: Optional[Union[Dict[str, Any], List[Dict[str, Any]], List[str]]] = None, + ) -> Any: return self.send_request(requests.post, path, body) - def put(self, path, body=None): + def put( + self, + path: str, + body: Optional[Union[Dict[str, Any], List[Dict[str, Any]], List[str]]] = None, + ) -> Any: return self.send_request(requests.put, path, body) - def delete(self, path, body=None): + def delete( + self, + path: str, + body: Optional[Union[Dict[str, Any], List[Dict[str, Any]], List[str]]] = None, + ) -> Any: return self.send_request(requests.delete, path, body) @staticmethod - def __to_json(request): + def __to_json( + request: requests.Response + ) -> Any: if request.content == b'': return request return request.json() @staticmethod - def __validate(request): + def __validate( + request: requests.Response + ) -> Any: try: request.raise_for_status() return HttpRequests.__to_json(request) except requests.exceptions.HTTPError as err: - raise MeiliSearchApiError(err, request) from err + raise MeiliSearchApiError(str(err), request) from err diff --git a/meilisearch/client.py b/meilisearch/client.py index d8177fc6..a795d44f 100644 --- a/meilisearch/client.py +++ b/meilisearch/client.py @@ -1,3 +1,5 @@ +from typing import Any, Dict, List, Optional + from meilisearch.index import Index from meilisearch.config import Config from meilisearch._httprequests import HttpRequests @@ -11,20 +13,22 @@ class Client(): MeiliSearch and its permissions. """ - def __init__(self, url, apiKey=None, timeout=None): + def __init__( + self, url: str, apiKey: Optional[str] = None, timeout: Optional[int] = None + ) -> None: """ Parameters ---------- - url : str + url: The url to the MeiliSearch API (ex: http://localhost:7700) - apiKey : str + apiKey: The optional API key for MeiliSearch """ self.config = Config(url, apiKey, timeout=timeout) self.http = HttpRequests(self.config) - def create_index(self, uid, options=None): + def create_index(self, uid: str, options: Optional[Dict[str, Any]] = None) -> Index: """Create an index. Parameters @@ -46,12 +50,12 @@ def create_index(self, uid, options=None): """ return Index.create(self.config, uid, options) - def get_indexes(self): + def get_indexes(self) -> List[Index]: """Get all indexes. Returns ------- - indexes: list + indexes: List of Index instances. Raises @@ -72,12 +76,12 @@ def get_indexes(self): for index in response ] - def get_raw_indexes(self): + def get_raw_indexes(self) -> List[Dict[str, Any]]: """Get all indexes in dictionary format. Returns ------- - indexes: list + indexes: List of indexes in dictionary format. (e.g [{ 'uid': 'movies' 'primaryKey': 'objectID' }]) Raises @@ -87,18 +91,18 @@ def get_raw_indexes(self): """ return self.http.get(self.config.paths.index) - def get_index(self, uid): + def get_index(self, uid: str) -> Index: """Get the index. This index should already exist. Parameters ---------- - uid: str + uid: UID of the index. Returns ------- - index : Index + index: An Index instance containing the information of the fetched index. Raises @@ -108,18 +112,18 @@ def get_index(self, uid): """ return Index(self.config, uid).fetch_info() - def get_raw_index(self, uid): + def get_raw_index(self, uid: str) -> Dict[str, Any]: """Get the index as a dictionary. This index should already exist. Parameters ---------- - uid: str + uid: UID of the index. Returns ------- - index : dict + index: An index in dictionary format. (e.g { 'uid': 'movies' 'primaryKey': 'objectID' }) Raises @@ -129,37 +133,37 @@ def get_raw_index(self, uid): """ return self.http.get(f'{self.config.paths.index}/{uid}') - def index(self, uid): + def index(self, uid: str) -> Index: """Create a local reference to an index identified by UID, without doing an HTTP call. Calling this method doesn't create an index in the MeiliSearch instance, but grants access to all the other methods in the Index class. Parameters ---------- - uid: str + uid: UID of the index. Returns ------- - index : Index + index: An Index instance. """ if uid is not None: return Index(self.config, uid=uid) raise Exception('The index UID should not be None') - def get_or_create_index(self, uid, options=None): + def get_or_create_index(self, uid: str, options: Optional[Dict[str, Any]] = None) -> Index: """Get an index, or create it if it doesn't exist. Parameters ---------- - uid: str + uid: UID of the index options (optional): dict Options passed during index creation (ex: primaryKey) Returns ------- - index : Index + index: An instance of Index containing the information of the retrieved or newly created index. Raises @@ -175,7 +179,7 @@ def get_or_create_index(self, uid, options=None): index_instance = self.create_index(uid, options) return index_instance - def get_all_stats(self): + def get_all_stats(self) -> Dict[str, Any]: """Get all stats of MeiliSearch Get information about database size and all indexes @@ -183,7 +187,7 @@ def get_all_stats(self): Returns ------- - stats: `dict` + stats: Dictionary containing stats about your MeiliSearch instance. Raises @@ -193,13 +197,12 @@ def get_all_stats(self): """ return self.http.get(self.config.paths.stat) - def health(self): + def health(self) -> Dict[str, str]: """Get health of the MeiliSearch server. - `200` HTTP status response when MeiliSearch is healthy. Returns ------- - health: `dict` + health: Dictionary containing the status of the MeiliSearch instance. Raises @@ -209,14 +212,8 @@ def health(self): """ return self.http.get(self.config.paths.health) - def is_healthy(self): + def is_healthy(self) -> bool: """Get health of the MeiliSearch server. - - `200` HTTP status response when MeiliSearch is healthy. - - Return - ------ - health: True | False """ try: self.health() @@ -224,14 +221,14 @@ def is_healthy(self): return False return True - def get_keys(self): + def get_keys(self) -> Dict[str, str]: """Get all keys. Get the public and private keys. Returns ------- - keys: dict + keys: Dictionary of keys and their information. https://docs.meilisearch.com/reference/api/keys.html#get-keys @@ -242,12 +239,12 @@ def get_keys(self): """ return self.http.get(self.config.paths.keys) - def get_version(self): + def get_version(self) -> Dict[str, str]: """Get version MeiliSearch Returns ------- - version: dict + version: Information about the version of MeiliSearch. Raises @@ -257,12 +254,12 @@ def get_version(self): """ return self.http.get(self.config.paths.version) - def version(self): + def version(self) -> Dict[str, str]: """Alias for get_version Returns ------- - version: dict + version: Information about the version of MeiliSearch. Raises @@ -272,12 +269,12 @@ def version(self): """ return self.get_version() - def create_dump(self): + def create_dump(self) -> Dict[str, str]: """Trigger the creation of a MeiliSearch dump. Returns ------- - Dump: dict + Dump: Information about the dump. https://docs.meilisearch.com/reference/api/dump.html#create-a-dump @@ -288,17 +285,17 @@ def create_dump(self): """ return self.http.post(self.config.paths.dumps) - def get_dump_status(self, uid): + def get_dump_status(self, uid: str) -> Dict[str, str]: """Retrieve the status of a MeiliSearch dump creation. Parameters ---------- - uid: str + uid: UID of the dump. Returns ------- - Dump status: dict + Dump status: Information about the dump status. https://docs.meilisearch.com/reference/api/dump.html#get-dump-status diff --git a/meilisearch/config.py b/meilisearch/config.py index 71f8dbf8..f8611b89 100644 --- a/meilisearch/config.py +++ b/meilisearch/config.py @@ -1,3 +1,6 @@ +from typing import Optional + + class Config: """ Client's credentials and configuration parameters @@ -23,13 +26,18 @@ class Paths(): attributes_for_faceting = 'attributes-for-faceting' dumps = 'dumps' - def __init__(self, url, api_key=None, timeout=None): + def __init__( + self, + url: str, + api_key: Optional[str] = None, + timeout: Optional[int] = None + ) -> None: """ Parameters ---------- - url: str + url: The url to the MeiliSearch API (ex: http://localhost:7700) - api_key (optional): str + api_key: The optional API key to access MeiliSearch """ diff --git a/meilisearch/errors.py b/meilisearch/errors.py index dd700c44..4e61da44 100644 --- a/meilisearch/errors.py +++ b/meilisearch/errors.py @@ -1,19 +1,21 @@ import json +from requests import Response + class MeiliSearchError(Exception): """Generic class for MeiliSearch error handling""" - def __init__(self, message): + def __init__(self, message: str) -> None: self.message = message super().__init__(self.message) - def __str__(self): + def __str__(self) -> str: return f'MeiliSearchError. Error message: {self.message}.' class MeiliSearchApiError(MeiliSearchError): """Error sent by MeiliSearch API""" - def __init__(self, error, request): + def __init__(self, error: str, request: Response) -> None: self.status_code = request.status_code if request.text: self.message = f'{json.loads(request.text)["message"]}' @@ -23,17 +25,17 @@ def __init__(self, error, request): self.message = error super().__init__(self.message) - def __str__(self): + def __str__(self) -> str: return f'MeiliSearchApiError. Error code: {self.error_code}. Error message: {self.message}. Error documentation: {self.error_link}' class MeiliSearchCommunicationError(MeiliSearchError): """Error when connecting to MeiliSearch""" - def __str__(self): + def __str__(self) -> str: return f'MeiliSearchCommunicationError, {self.message}' class MeiliSearchTimeoutError(MeiliSearchError): """Error when MeiliSearch operation takes longer than expected""" - def __str__(self): + def __str__(self) -> str: return f'MeiliSearchTimeoutError, {self.message}' diff --git a/meilisearch/index.py b/meilisearch/index.py index 9d67d851..dde161f3 100644 --- a/meilisearch/index.py +++ b/meilisearch/index.py @@ -1,7 +1,12 @@ import urllib from datetime import datetime from time import sleep +from typing import Any, Dict, Generator, List, Optional, Union + +from requests import Response + from meilisearch._httprequests import HttpRequests +from meilisearch.config import Config from meilisearch.errors import MeiliSearchTimeoutError # pylint: disable=too-many-public-methods @@ -13,15 +18,22 @@ class Index(): https://docs.meilisearch.com/reference/api/indexes.html """ - def __init__(self, config, uid, primary_key=None, created_at=None, updated_at=None): + def __init__( + self, + config: Config, + uid: str, + primary_key: Optional[str] = None, + created_at: Optional[Union[datetime, str]] = None, + updated_at: Optional[Union[datetime, str]] = None, + ) -> None: """ Parameters ---------- - config : dict + config: Config object containing permission and location of MeiliSearch. - uid: str + uid: UID of the index on which to perform the index actions. - primary_key (optional): str + primary_key: Primary-key of the index. """ self.config = config @@ -31,7 +43,7 @@ def __init__(self, config, uid, primary_key=None, created_at=None, updated_at=No self.created_at = self._iso_to_date_time(created_at) self.updated_at = self._iso_to_date_time(updated_at) - def delete(self): + def delete(self) -> Response: """Delete the index. Raises @@ -39,29 +51,25 @@ def delete(self): MeiliSearchApiError An error containing details about why MeiliSearch can't process your request. MeiliSearch error codes are described here: https://docs.meilisearch.com/errors/#meilisearch-errors """ + return self.http.delete(f'{self.config.paths.index}/{self.uid}') - def update(self, **body): + def update(self, **body: Dict[str, Any]) -> 'Index': """Update the index primary-key. Parameters ---------- - body: **kwargs + body: Accepts primaryKey as an updatable parameter. Ex: index.update(primaryKey='name') - Returns - ------- - index : Index - An instance of Index - Raises ------ MeiliSearchApiError An error containing details about why MeiliSearch can't process your request. MeiliSearch error codes are described here: https://docs.meilisearch.com/errors/#meilisearch-errors """ payload = {} - primary_key = body.get('primaryKey', None) + primary_key = body.get('primaryKey') if primary_key is not None: payload['primaryKey'] = primary_key response = self.http.put(f'{self.config.paths.index}/{self.uid}', payload) @@ -70,14 +78,9 @@ def update(self, **body): self.updated_at = self._iso_to_date_time(response['updatedAt']) return self - def fetch_info(self): + def fetch_info(self) -> 'Index': """Fetch the info of the index. - Returns - ------- - index : Index - An instance of Index - Raises ------ MeiliSearchApiError @@ -89,14 +92,9 @@ def fetch_info(self): self.updated_at = self._iso_to_date_time(index_dict['updatedAt']) return self - def get_primary_key(self): + def get_primary_key(self) -> Optional[str]: """Get the primary key. - Returns - ------- - primary_key: str | None - String containing the primary key. - Raises ------ MeiliSearchApiError @@ -105,21 +103,16 @@ def get_primary_key(self): return self.fetch_info().primary_key @classmethod - def create(cls, config, uid, options=None): + def create(cls, config: Config, uid: str, options: Optional[Dict[str, Any]] = None) -> "Index": """Create the index. Parameters ---------- - uid: str + uid: UID of the index. - options: dict, optional + options: Options passed during index creation (ex: { 'primaryKey': 'name' }). - Returns - ------- - index : Index - An instance of Index containing the information of the newly created index. - Raises ------ MeiliSearchApiError @@ -129,14 +122,15 @@ def create(cls, config, uid, options=None): options = {} payload = {**options, 'uid': uid} index_dict = HttpRequests(config).post(config.paths.index, payload) + return cls(config, index_dict['uid'], index_dict['primaryKey']) - def get_all_update_status(self): + def get_all_update_status(self) -> List[Dict[str, Any]]: """Get all update status Returns ------- - update: list + update: List of all enqueued and processed actions of the index. Raises @@ -148,18 +142,18 @@ def get_all_update_status(self): f'{self.config.paths.index}/{self.uid}/{self.config.paths.update}' ) - def get_update_status(self, update_id): + def get_update_status(self, update_id: int) -> Dict[str, Any]: """Get one update status Parameters ---------- - update_id: int + update_id: identifier of the update to retrieve Returns ------- - update: list - List containing the details of the update status. + update: + A Dictionary containing the details of the update status. Raises ------ @@ -170,21 +164,25 @@ def get_update_status(self, update_id): f'{self.config.paths.index}/{self.uid}/{self.config.paths.update}/{update_id}' ) - def wait_for_pending_update(self, update_id, timeout_in_ms=5000, interval_in_ms=50): + def wait_for_pending_update( + self, update_id: int, + timeout_in_ms: int = 5000, + interval_in_ms: int = 50, + ) -> Dict[str, Any]: """Wait until MeiliSearch processes an update, and get its status. Parameters ---------- - update_id: int + update_id: identifier of the update to retrieve - timeout_in_ms (optional): int + timeout_in_ms (optional): time the method should wait before raising a MeiliSearchTimeoutError - interval_in_ms (optional): int + interval_in_ms (optional): time interval the method should wait (sleep) between requests Returns ------- - update: dict + update: Dictionary containing the details of the processed update status. Raises @@ -193,9 +191,10 @@ def wait_for_pending_update(self, update_id, timeout_in_ms=5000, interval_in_ms= An error containing details about why MeiliSearch can't process your request. MeiliSearch error codes are described here: https://docs.meilisearch.com/errors/#meilisearch-errors """ start_time = datetime.now() - elapsed_time = 0 + elapsed_time = 0. while elapsed_time < timeout_in_ms: get_update = self.get_update_status(update_id) + if get_update['status'] != 'enqueued': return get_update sleep(interval_in_ms / 1000) @@ -203,7 +202,7 @@ def wait_for_pending_update(self, update_id, timeout_in_ms=5000, interval_in_ms= elapsed_time = time_delta.seconds * 1000 + time_delta.microseconds / 1000 raise MeiliSearchTimeoutError(f'timeout of ${timeout_in_ms}ms has exceeded on process ${update_id} when waiting for pending update to resolve.') - def get_stats(self): + def get_stats(self) -> Dict[str, Any]: """Get stats of the index. Get information about the number of documents, field frequencies, ... @@ -211,7 +210,7 @@ def get_stats(self): Returns ------- - stats: dict + stats: Dictionary containing stats about the given index. Raises @@ -223,20 +222,20 @@ def get_stats(self): f'{self.config.paths.index}/{self.uid}/{self.config.paths.stat}' ) - def search(self, query, opt_params=None): + def search(self, query: str, opt_params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: """Search in the index. Parameters ---------- - query: str + query: String containing the searched word(s) - opt_params (optional): dict + opt_params (optional): Dictionary containing optional query parameters https://docs.meilisearch.com/reference/api/search.html#search-in-an-index Returns ------- - results: dict + results: Dictionary with hits, offset, limit, processingTime and initial query Raises @@ -255,17 +254,17 @@ def search(self, query, opt_params=None): body=body ) - def get_document(self, document_id): + def get_document(self, document_id: str) -> Dict[str, Any]: """Get one document with given document identifier. Parameters ---------- - document_id: str + document_id: Unique identifier of the document. Returns ------- - document: dict + document: Dictionary containing the documents information. Raises @@ -277,18 +276,18 @@ def get_document(self, document_id): f'{self.config.paths.index}/{self.uid}/{self.config.paths.document}/{document_id}' ) - def get_documents(self, parameters=None): + def get_documents(self, parameters: Optional[Dict[str, Any]] = None) -> List[Dict[str, Any]]: """Get a set of documents from the index. Parameters ---------- - parameters (optional): dict + parameters (optional): parameters accepted by the get documents route: https://docs.meilisearch.com/reference/api/documents.html#get-all-documents Returns ------- - document: dict - Dictionary containing the documents information. + document: + List of dictionaries containing the documents information. Raises ------ @@ -302,19 +301,23 @@ def get_documents(self, parameters=None): f'{self.config.paths.index}/{self.uid}/{self.config.paths.document}?{urllib.parse.urlencode(parameters)}' ) - def add_documents(self, documents, primary_key=None): + def add_documents( + self, + documents: List[Dict[str, Any]], + primary_key: Optional[str] = None, + ) -> Dict[str, int]: """Add documents to the index. Parameters ---------- - documents: list + documents: List of documents. Each document should be a dictionary. - primary_key (optional): string + primary_key (optional): The primary-key used in index. Ignored if already set up. Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -330,21 +333,26 @@ def add_documents(self, documents, primary_key=None): url = f'{self.config.paths.index}/{self.uid}/{self.config.paths.document}?{primary_key}' return self.http.post(url, documents) - def add_documents_in_batches(self, documents, batch_size=1000, primary_key=None): + def add_documents_in_batches( + self, + documents: List[Dict[str, Any]], + batch_size: int = 1000, + primary_key: Optional[str] = None, + ) -> List[Dict[str, int]]: """Add documents to the index in batches. Parameters ---------- - documents: list + documents: List of documents. Each document should be a dictionary. - batch_size (optional): int + batch_size (optional): The number of documents that should be included in each batch. Default = 1000 - primary_key (optional): string + primary_key (optional): The primary-key used in index. Ignored if already set up. Returns ------- - update: list[dict] + update: List of dictionaries containing an update ids to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -363,19 +371,23 @@ def add_documents_in_batches(self, documents, batch_size=1000, primary_key=None) return update_ids - def update_documents(self, documents, primary_key=None): + def update_documents( + self, + documents: List[Dict[str, Any]], + primary_key: Optional[str] = None + ) -> Dict[str, int]: """Update documents in the index. Parameters ---------- - documents: list + documents: List of documents. Each document should be a dictionary. - primary_key (optional): string + primary_key (optional): The primary-key used in index. Ignored if already set up Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -391,21 +403,26 @@ def update_documents(self, documents, primary_key=None): url = f'{self.config.paths.index}/{self.uid}/{self.config.paths.document}?{primary_key}' return self.http.put(url, documents) - def update_documents_in_batches(self, documents, batch_size=1000, primary_key=None): + def update_documents_in_batches( + self, + documents: List[Dict[str, Any]], + batch_size: int = 1000, + primary_key: Optional[str] = None + ) -> List[Dict[str, Any]]: """Update documents to the index in batches. Parameters ---------- - documents: list + documents: List of documents. Each document should be a dictionary. - batch_size (optional): int + batch_size (optional): The number of documents that should be included in each batch. Default = 1000 - primary_key (optional): string + primary_key (optional): The primary-key used in index. Ignored if already set up. Returns ------- - update: list[dict] + update: List of dictionaries containing an update ids to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -424,17 +441,17 @@ def update_documents_in_batches(self, documents, batch_size=1000, primary_key=No return update_ids - def delete_document(self, document_id): + def delete_document(self, document_id: str) -> Dict[str, Any]: """Delete one document from the index. Parameters ---------- - document_id: str + document_id: Unique identifier of the document. Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -447,17 +464,17 @@ def delete_document(self, document_id): f'{self.config.paths.index}/{self.uid}/{self.config.paths.document}/{document_id}' ) - def delete_documents(self, ids): + def delete_documents(self, ids: List[str]) -> Dict[str, int]: """Delete multiple documents from the index. Parameters ---------- - list: list + list: List of unique identifiers of documents. Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -471,12 +488,12 @@ def delete_documents(self, ids): ids ) - def delete_all_documents(self): + def delete_all_documents(self) -> Dict[str, int]: """Delete all documents from the index. Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -489,17 +506,16 @@ def delete_all_documents(self): f'{self.config.paths.index}/{self.uid}/{self.config.paths.document}' ) - # GENERAL SETTINGS ROUTES - def get_settings(self): + def get_settings(self) -> Dict[str, Any]: """Get settings of the index. https://docs.meilisearch.com/reference/api/settings.html Returns ------- - settings: dict + settings Dictionary containing the settings of the index. Raises @@ -511,21 +527,21 @@ def get_settings(self): f'{self.config.paths.index}/{self.uid}/{self.config.paths.setting}' ) - def update_settings(self, body): + def update_settings(self, body: Dict[str, Any]) -> Dict[str, int]: """Update settings of the index. https://docs.meilisearch.com/reference/api/settings.html#update-settings Parameters ---------- - body: dict + body: Dictionary containing the settings of the index. More information: https://docs.meilisearch.com/reference/api/settings.html#update-settings Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -539,14 +555,14 @@ def update_settings(self, body): body ) - def reset_settings(self): + def reset_settings(self) -> Dict[str, int]: """Reset settings of the index to default values. https://docs.meilisearch.com/reference/api/settings.html#reset-settings Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -561,7 +577,7 @@ def reset_settings(self): # RANKING RULES SUB-ROUTES - def get_ranking_rules(self): + def get_ranking_rules(self) -> List[str]: """ Get ranking rules of the index. @@ -579,18 +595,18 @@ def get_ranking_rules(self): self.__settings_url_for(self.config.paths.ranking_rules) ) - def update_ranking_rules(self, body): + def update_ranking_rules(self, body: List[str]) -> Dict[str, int]: """ Update ranking rules of the index. Parameters ---------- - body: list + body: List containing the ranking rules. Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -604,12 +620,12 @@ def update_ranking_rules(self, body): body ) - def reset_ranking_rules(self): + def reset_ranking_rules(self) -> Dict[str, int]: """Reset ranking rules of the index to default values. Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -622,16 +638,15 @@ def reset_ranking_rules(self): self.__settings_url_for(self.config.paths.ranking_rules), ) - # DISTINCT ATTRIBUTE SUB-ROUTES - def get_distinct_attribute(self): + def get_distinct_attribute(self) -> Optional[str]: """ Get distinct attribute of the index. Returns ------- - settings: str | None + settings: String containing the distinct attribute of the index. If no distinct attribute None is returned. Raises @@ -643,18 +658,18 @@ def get_distinct_attribute(self): self.__settings_url_for(self.config.paths.distinct_attribute) ) - def update_distinct_attribute(self, body): + def update_distinct_attribute(self, body: Dict[str, Any]) -> Dict[str, int]: """ Update distinct attribute of the index. Parameters ---------- - body: str + body: String containing the distinct attribute. Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -668,12 +683,12 @@ def update_distinct_attribute(self, body): body ) - def reset_distinct_attribute(self): + def reset_distinct_attribute(self) -> Dict[str, int]: """Reset distinct attribute of the index to default values. Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -688,13 +703,13 @@ def reset_distinct_attribute(self): # SEARCHABLE ATTRIBUTES SUB-ROUTES - def get_searchable_attributes(self): + def get_searchable_attributes(self) -> List[str]: """ Get searchable attributes of the index. Returns ------- - settings: list + settings: List containing the searchable attributes of the index. Raises @@ -706,18 +721,18 @@ def get_searchable_attributes(self): self.__settings_url_for(self.config.paths.searchable_attributes) ) - def update_searchable_attributes(self, body): + def update_searchable_attributes(self, body: List[str]) -> Dict[str, int]: """ Update searchable attributes of the index. Parameters ---------- - body: list + body: List containing the searchable attributes. Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -731,12 +746,12 @@ def update_searchable_attributes(self, body): body ) - def reset_searchable_attributes(self): + def reset_searchable_attributes(self) -> Dict[str, int]: """Reset searchable attributes of the index to default values. Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -751,13 +766,13 @@ def reset_searchable_attributes(self): # DISPLAYED ATTRIBUTES SUB-ROUTES - def get_displayed_attributes(self): + def get_displayed_attributes(self) -> List[str]: """ Get displayed attributes of the index. Returns ------- - settings: list + settings: List containing the displayed attributes of the index. Raises @@ -769,18 +784,18 @@ def get_displayed_attributes(self): self.__settings_url_for(self.config.paths.displayed_attributes) ) - def update_displayed_attributes(self, body): + def update_displayed_attributes(self, body: List[str]) -> Dict[str, int]: """ Update displayed attributes of the index. Parameters ---------- - body: list + body: List containing the displayed attributes. Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -794,12 +809,12 @@ def update_displayed_attributes(self, body): body ) - def reset_displayed_attributes(self): + def reset_displayed_attributes(self) -> Dict[str, int]: """Reset displayed attributes of the index to default values. Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -814,13 +829,13 @@ def reset_displayed_attributes(self): # STOP WORDS SUB-ROUTES - def get_stop_words(self): + def get_stop_words(self) -> List[str]: """ Get stop words of the index. Returns ------- - settings: list + settings: List containing the stop words of the index. Raises @@ -832,7 +847,7 @@ def get_stop_words(self): self.__settings_url_for(self.config.paths.stop_words) ) - def update_stop_words(self, body): + def update_stop_words(self, body: List[str]) -> Dict[str, int]: """ Update stop words of the index. @@ -843,7 +858,7 @@ def update_stop_words(self, body): Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -857,12 +872,12 @@ def update_stop_words(self, body): body ) - def reset_stop_words(self): + def reset_stop_words(self) -> Dict[str, int]: """Reset stop words of the index to default values. Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -877,7 +892,7 @@ def reset_stop_words(self): # SYNONYMS SUB-ROUTES - def get_synonyms(self): + def get_synonyms(self) -> Dict[str, List[str]]: """ Get synonyms of the index. @@ -895,7 +910,7 @@ def get_synonyms(self): self.__settings_url_for(self.config.paths.synonyms) ) - def update_synonyms(self, body): + def update_synonyms(self, body: Dict[str, List[str]]) -> Dict[str, int]: """ Update synonyms of the index. @@ -920,12 +935,12 @@ def update_synonyms(self, body): body ) - def reset_synonyms(self): + def reset_synonyms(self) -> Dict[str, int]: """Reset synonyms of the index to default values. Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -940,13 +955,13 @@ def reset_synonyms(self): # ATTRIBUTES FOR FACETING SUB-ROUTES - def get_attributes_for_faceting(self): + def get_attributes_for_faceting(self) -> List[str]: """ Get attributes for faceting of the index. Returns ------- - settings: list + settings: List containing the attributes for faceting of the index Raises @@ -958,18 +973,18 @@ def get_attributes_for_faceting(self): self.__settings_url_for(self.config.paths.attributes_for_faceting) ) - def update_attributes_for_faceting(self, body): + def update_attributes_for_faceting(self, body: List[str]) -> Dict[str, int]: """ Update attributes for faceting of the index. Parameters ---------- - body: list + body: List containing the attributes for faceting. Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -983,12 +998,12 @@ def update_attributes_for_faceting(self, body): body ) - def reset_attributes_for_faceting(self): + def reset_attributes_for_faceting(self) -> Dict[str, int]: """Reset attributes for faceting of the index to default values. Returns ------- - update: dict + update: Dictionary containing an update id to track the action: https://docs.meilisearch.com/reference/api/updates.html#get-an-update-status @@ -1002,13 +1017,15 @@ def reset_attributes_for_faceting(self): ) @staticmethod - def _batch(documents, batch_size): + def _batch( + documents: List[Dict[str, Any]], batch_size: int + ) -> Generator[List[Dict[str, Any]], None, None]: total_len = len(documents) for i in range(0, total_len, batch_size): yield documents[i : i + batch_size] @staticmethod - def _iso_to_date_time(iso_date): + def _iso_to_date_time(iso_date: Optional[Union[datetime, str]]) -> Optional[datetime]: """ MeiliSearch returns the date time information in iso format. Python's implementation of datetime can only handle up to 6 digits in microseconds, however MeiliSearch sometimes @@ -1031,5 +1048,5 @@ def _iso_to_date_time(iso_date): return datetime.strptime(reduced, "%Y-%m-%dT%H:%M:%S.%fZ") - def __settings_url_for(self, sub_route): + def __settings_url_for(self, sub_route: str) -> str: return f'{self.config.paths.index}/{self.uid}/{self.config.paths.setting}/{sub_route}' diff --git a/meilisearch/py.typed b/meilisearch/py.typed new file mode 100644 index 00000000..e69de29b diff --git a/mypy.ini b/mypy.ini new file mode 100644 index 00000000..ea326688 --- /dev/null +++ b/mypy.ini @@ -0,0 +1,5 @@ +[mypy] +disallow_untyped_defs = True + +[mypy-meilisearch.tests.*] +disallow_untyped_defs = False diff --git a/setup.py b/setup.py index 161f9f26..a1ce6a4b 100644 --- a/setup.py +++ b/setup.py @@ -28,6 +28,9 @@ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ], + package_data = { + "meilisearch": ["py.typed"], + }, include_package_data=True, python_requires=">=3", ) diff --git a/tox.ini b/tox.ini index d13244ec..f9381176 100644 --- a/tox.ini +++ b/tox.ini @@ -1,14 +1,22 @@ [tox] -envlist = pylint, py36, py37, py38, py39 +envlist = pylint, mypy, py36, py37, py38, py39 [testenv:pylint] whitelist_externals = pipenv python deps = pylint -commands = +commands = pipenv run pylint meilisearch +[testenv:mypy] +whitelist_externals = + pipenv + python +deps = mypy +commands = + pipenv run mypy meilisearch + [testenv] whitelist_externals = pipenv