From 6e684b7cadb92d9952f772915dd88f9a702b8c0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Francisco=20Calvo?= Date: Thu, 9 May 2024 11:15:22 +0200 Subject: [PATCH 1/5] feat: migrate API v0 /api/_info endpoint to /api/v1/version --- src/argilla_server/apis/routes.py | 2 ++ src/argilla_server/apis/v1/handlers/info.py | 25 ++++++++++++++++++ src/argilla_server/schemas/v1/info.py | 19 ++++++++++++++ tests/unit/api/v1/info/__init__.py | 13 +++++++++ tests/unit/api/v1/info/test_get_version.py | 29 +++++++++++++++++++++ 5 files changed, 88 insertions(+) create mode 100644 src/argilla_server/apis/v1/handlers/info.py create mode 100644 src/argilla_server/schemas/v1/info.py create mode 100644 tests/unit/api/v1/info/__init__.py create mode 100644 tests/unit/api/v1/info/test_get_version.py diff --git a/src/argilla_server/apis/routes.py b/src/argilla_server/apis/routes.py index 0ba99328..e075e58d 100644 --- a/src/argilla_server/apis/routes.py +++ b/src/argilla_server/apis/routes.py @@ -42,6 +42,7 @@ from argilla_server.apis.v1.handlers import ( fields as fields_v1, ) +from argilla_server.apis.v1.handlers import info as info_v1 from argilla_server.apis.v1.handlers import ( metadata_properties as metadata_properties_v1, ) @@ -114,6 +115,7 @@ def create_api_v1(): APIErrorHandler.configure_app(api_v1) for router in [ + info_v1.router, authentication_v1.router, datasets_v1.router, fields_v1.router, diff --git a/src/argilla_server/apis/v1/handlers/info.py b/src/argilla_server/apis/v1/handlers/info.py new file mode 100644 index 00000000..d1d4c890 --- /dev/null +++ b/src/argilla_server/apis/v1/handlers/info.py @@ -0,0 +1,25 @@ +# Copyright 2021-present, the Recognai S.L. team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from fastapi import APIRouter + +from argilla_server._version import __version__ +from argilla_server.schemas.v1.info import Version + +router = APIRouter(tags=["info"]) + + +@router.get("/version", response_model=Version) +async def get_version(): + return Version(version=__version__) diff --git a/src/argilla_server/schemas/v1/info.py b/src/argilla_server/schemas/v1/info.py new file mode 100644 index 00000000..dd528fc5 --- /dev/null +++ b/src/argilla_server/schemas/v1/info.py @@ -0,0 +1,19 @@ +# Copyright 2021-present, the Recognai S.L. team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from argilla_server.pydantic_v1 import BaseModel + + +class Version(BaseModel): + version: str diff --git a/tests/unit/api/v1/info/__init__.py b/tests/unit/api/v1/info/__init__.py new file mode 100644 index 00000000..55be4179 --- /dev/null +++ b/tests/unit/api/v1/info/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021-present, the Recognai S.L. team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. diff --git a/tests/unit/api/v1/info/test_get_version.py b/tests/unit/api/v1/info/test_get_version.py new file mode 100644 index 00000000..c00f576f --- /dev/null +++ b/tests/unit/api/v1/info/test_get_version.py @@ -0,0 +1,29 @@ +# Copyright 2021-present, the Recognai S.L. team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from argilla_server._version import __version__ +from httpx import AsyncClient + + +@pytest.mark.asyncio +class TestGetVersion: + def url(self) -> str: + return "/api/v1/version" + + async def test_get_version(self, async_client: AsyncClient): + response = await async_client.get(self.url()) + + assert response.status_code == 200 + assert response.json() == {"version": __version__} From d30a78101ea59d023232b31fdbff7fa83b2c9e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Francisco=20Calvo?= Date: Thu, 9 May 2024 12:00:40 +0200 Subject: [PATCH 2/5] chore: update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 647a8713..d4724ba5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ These are the section headers that we use: - Added `POST /api/v1/token` endpoint to generate a new API token for a user. ([#138](https://github.com/argilla-io/argilla-server/pull/138)) - Added `GET /api/v1/me` endpoint to get the current user information. ([#140](https://github.com/argilla-io/argilla-server/pull/140)) +- Added `GET /api/v1/version` endpoin to get the current Argilla version. ([#162](https://github.com/argilla-io/argilla-server/pull/162)) ## [Unreleased]() From 270d6a2272f4207890217e0c7bc8c999fae0ef98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Francisco=20Calvo?= Date: Fri, 10 May 2024 11:52:39 +0200 Subject: [PATCH 3/5] feat: migrate API v0 /api/_status endpoint to /api/v1/status --- src/argilla_server/apis/v1/handlers/info.py | 15 ++++-- src/argilla_server/contexts/info.py | 60 +++++++++++++++++++++ src/argilla_server/schemas/v1/info.py | 6 +++ src/argilla_server/services/info.py | 2 + tests/unit/api/v1/info/test_get_status.py | 33 ++++++++++++ 5 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 src/argilla_server/contexts/info.py create mode 100644 tests/unit/api/v1/info/test_get_status.py diff --git a/src/argilla_server/apis/v1/handlers/info.py b/src/argilla_server/apis/v1/handlers/info.py index d1d4c890..8a9df062 100644 --- a/src/argilla_server/apis/v1/handlers/info.py +++ b/src/argilla_server/apis/v1/handlers/info.py @@ -14,12 +14,21 @@ from fastapi import APIRouter -from argilla_server._version import __version__ -from argilla_server.schemas.v1.info import Version +from argilla_server.contexts import info +from argilla_server.schemas.v1.info import Status, Version router = APIRouter(tags=["info"]) @router.get("/version", response_model=Version) async def get_version(): - return Version(version=__version__) + return Version(version=info.argilla_version()) + + +@router.get("/status", response_model=Status) +async def get_status(): + return Status( + version=info.argilla_version(), + elasticsearch=info.elasticsearch_status(), + memory=info.memory_status(), + ) diff --git a/src/argilla_server/contexts/info.py b/src/argilla_server/contexts/info.py new file mode 100644 index 00000000..47065d58 --- /dev/null +++ b/src/argilla_server/contexts/info.py @@ -0,0 +1,60 @@ +# Copyright 2021-present, the Recognai S.L. team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +import psutil + +from argilla_server._version import __version__ +from argilla_server.daos.backend import GenericElasticEngineBackend + + +def argilla_version() -> str: + return __version__ + + +def elasticsearch_status() -> dict: + return GenericElasticEngineBackend.get_instance().client.get_cluster_info() + + +def memory_status() -> dict: + process = psutil.Process(os.getpid()) + + return {k: _memory_size(v) for k, v in process.memory_info()._asdict().items()} + + +def _memory_size(bytes) -> str: + system = [ + (1024**5, "P"), + (1024**4, "T"), + (1024**3, "G"), + (1024**2, "M"), + (1024**1, "K"), + (1024**0, "B"), + ] + + factor, suffix = None, None + for factor, suffix in system: + if bytes >= factor: + break + + amount = int(bytes / factor) + if isinstance(suffix, tuple): + singular, multiple = suffix + if amount == 1: + suffix = singular + else: + suffix = multiple + + return str(amount) + suffix diff --git a/src/argilla_server/schemas/v1/info.py b/src/argilla_server/schemas/v1/info.py index dd528fc5..dfa0ba80 100644 --- a/src/argilla_server/schemas/v1/info.py +++ b/src/argilla_server/schemas/v1/info.py @@ -17,3 +17,9 @@ class Version(BaseModel): version: str + + +class Status(BaseModel): + version: str + elasticsearch: dict + memory: dict diff --git a/src/argilla_server/services/info.py b/src/argilla_server/services/info.py index 496f9a75..79fcd4fc 100644 --- a/src/argilla_server/services/info.py +++ b/src/argilla_server/services/info.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +# TODO: Once we delete all API v0 endpoints, we can remove this file. + import os from typing import Any, Dict diff --git a/tests/unit/api/v1/info/test_get_status.py b/tests/unit/api/v1/info/test_get_status.py new file mode 100644 index 00000000..d05b8813 --- /dev/null +++ b/tests/unit/api/v1/info/test_get_status.py @@ -0,0 +1,33 @@ +# Copyright 2021-present, the Recognai S.L. team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from argilla_server._version import __version__ +from httpx import AsyncClient + + +@pytest.mark.asyncio +class TestGetStatus: + def url(self) -> str: + return "/api/v1/status" + + async def test_get_status(self, async_client: AsyncClient): + response = await async_client.get(self.url()) + + assert response.status_code == 200 + + response_json = response.json() + assert response_json["version"] == __version__ + assert "elasticsearch" in response_json + assert "memory" in response_json From 3c03e0acc4f2aa67dda4580c39e78e54c96abc17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Francisco=20Calvo?= Date: Fri, 10 May 2024 12:00:04 +0200 Subject: [PATCH 4/5] chore: update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4724ba5..6fe0538f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,8 @@ These are the section headers that we use: - Added `POST /api/v1/token` endpoint to generate a new API token for a user. ([#138](https://github.com/argilla-io/argilla-server/pull/138)) - Added `GET /api/v1/me` endpoint to get the current user information. ([#140](https://github.com/argilla-io/argilla-server/pull/140)) -- Added `GET /api/v1/version` endpoin to get the current Argilla version. ([#162](https://github.com/argilla-io/argilla-server/pull/162)) +- Added `GET /api/v1/version` endpoint to get the current Argilla version. ([#162](https://github.com/argilla-io/argilla-server/pull/162)) +- Added `GET /api/v1/status` endpoint to get Argilla service status. ([#165](https://github.com/argilla-io/argilla-server/pull/165)) ## [Unreleased]() From d2b6f8b61c327f1a01155ee14ebe43e371de58a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Francisco=20Calvo?= Date: Fri, 10 May 2024 18:03:42 +0200 Subject: [PATCH 5/5] feat: apply feedback suggestions --- src/argilla_server/apis/v1/handlers/info.py | 7 ++++--- src/argilla_server/contexts/info.py | 5 ----- src/argilla_server/schemas/v1/info.py | 2 +- src/argilla_server/search_engine/base.py | 4 ++++ src/argilla_server/search_engine/elasticsearch.py | 3 +++ src/argilla_server/search_engine/opensearch.py | 3 +++ tests/unit/api/v1/info/test_get_status.py | 7 +++++-- 7 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/argilla_server/apis/v1/handlers/info.py b/src/argilla_server/apis/v1/handlers/info.py index 8a9df062..7a1f1d27 100644 --- a/src/argilla_server/apis/v1/handlers/info.py +++ b/src/argilla_server/apis/v1/handlers/info.py @@ -12,10 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from fastapi import APIRouter +from fastapi import APIRouter, Depends from argilla_server.contexts import info from argilla_server.schemas.v1.info import Status, Version +from argilla_server.search_engine import SearchEngine, get_search_engine router = APIRouter(tags=["info"]) @@ -26,9 +27,9 @@ async def get_version(): @router.get("/status", response_model=Status) -async def get_status(): +async def get_status(search_engine: SearchEngine = Depends(get_search_engine)): return Status( version=info.argilla_version(), - elasticsearch=info.elasticsearch_status(), + search_engine=await search_engine.info(), memory=info.memory_status(), ) diff --git a/src/argilla_server/contexts/info.py b/src/argilla_server/contexts/info.py index 47065d58..704cc929 100644 --- a/src/argilla_server/contexts/info.py +++ b/src/argilla_server/contexts/info.py @@ -17,17 +17,12 @@ import psutil from argilla_server._version import __version__ -from argilla_server.daos.backend import GenericElasticEngineBackend def argilla_version() -> str: return __version__ -def elasticsearch_status() -> dict: - return GenericElasticEngineBackend.get_instance().client.get_cluster_info() - - def memory_status() -> dict: process = psutil.Process(os.getpid()) diff --git a/src/argilla_server/schemas/v1/info.py b/src/argilla_server/schemas/v1/info.py index dfa0ba80..8ec7da13 100644 --- a/src/argilla_server/schemas/v1/info.py +++ b/src/argilla_server/schemas/v1/info.py @@ -21,5 +21,5 @@ class Version(BaseModel): class Status(BaseModel): version: str - elasticsearch: dict + search_engine: dict memory: dict diff --git a/src/argilla_server/search_engine/base.py b/src/argilla_server/search_engine/base.py index 2e0f402e..7396c3da 100644 --- a/src/argilla_server/search_engine/base.py +++ b/src/argilla_server/search_engine/base.py @@ -264,6 +264,10 @@ async def new_instance(cls) -> "SearchEngine": async def close(self): pass + @abstractmethod + async def info(self) -> dict: + pass + @classmethod def register(cls, engine_name: str): def decorator(engine_class): diff --git a/src/argilla_server/search_engine/elasticsearch.py b/src/argilla_server/search_engine/elasticsearch.py index c945e471..44b2d5c4 100644 --- a/src/argilla_server/search_engine/elasticsearch.py +++ b/src/argilla_server/search_engine/elasticsearch.py @@ -64,6 +64,9 @@ async def new_instance(cls) -> "ElasticSearchEngine": async def close(self): await self.client.close() + async def info(self) -> dict: + return await self.client.info() + def _mapping_for_vector_settings(self, vector_settings: VectorSettings) -> dict: return { es_field_for_vector_settings(vector_settings): { diff --git a/src/argilla_server/search_engine/opensearch.py b/src/argilla_server/search_engine/opensearch.py index 078ae051..6333dd64 100644 --- a/src/argilla_server/search_engine/opensearch.py +++ b/src/argilla_server/search_engine/opensearch.py @@ -56,6 +56,9 @@ async def new_instance(cls) -> "OpenSearchEngine": async def close(self): await self.client.close() + async def info(self) -> dict: + return await self.client.info() + def _configure_index_settings(self): base_settings = super()._configure_index_settings() return {**base_settings, "index.knn": False} diff --git a/tests/unit/api/v1/info/test_get_status.py b/tests/unit/api/v1/info/test_get_status.py index d05b8813..e337bf20 100644 --- a/tests/unit/api/v1/info/test_get_status.py +++ b/tests/unit/api/v1/info/test_get_status.py @@ -14,6 +14,7 @@ import pytest from argilla_server._version import __version__ +from argilla_server.search_engine import SearchEngine from httpx import AsyncClient @@ -22,12 +23,14 @@ class TestGetStatus: def url(self) -> str: return "/api/v1/status" - async def test_get_status(self, async_client: AsyncClient): + async def test_get_status(self, async_client: AsyncClient, mock_search_engine: SearchEngine): + mock_search_engine.info.return_value = {} + response = await async_client.get(self.url()) assert response.status_code == 200 response_json = response.json() assert response_json["version"] == __version__ - assert "elasticsearch" in response_json + assert "search_engine" in response_json assert "memory" in response_json