diff --git a/CHANGELOG.md b/CHANGELOG.md index 08e2bfe..cf98949 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ and this project adheres to ## [Unreleased] +### Added + +- Add `get_plugin(name)` method to fetch plugin description by name + ([dbb4d57], [#85]) + ### Changed - README: update PyPI badges and add supported Python versions @@ -100,6 +105,7 @@ and this project adheres to [0.1.9]: https://github.com/LeakIX/LeakIXClient-Python/releases/tag/v0.1.9 +[dbb4d57]: https://github.com/LeakIX/LeakIXClient-Python/commit/dbb4d57 [d9e4bf8]: https://github.com/LeakIX/LeakIXClient-Python/commit/d9e4bf8 [87b68f6]: https://github.com/LeakIX/LeakIXClient-Python/commit/87b68f6 [591c046]: https://github.com/LeakIX/LeakIXClient-Python/commit/591c046 @@ -124,6 +130,7 @@ and this project adheres to [4dd4948]: https://github.com/LeakIX/LeakIXClient-Python/commit/4dd4948 +[#85]: https://github.com/LeakIX/LeakIXClient-Python/issues/85 [#84]: https://github.com/LeakIX/LeakIXClient-Python/issues/84 [#66]: https://github.com/LeakIX/LeakIXClient-Python/pull/66 [#65]: https://github.com/LeakIX/LeakIXClient-Python/issues/65 diff --git a/leakix/async_client.py b/leakix/async_client.py index 89c85a5..24def11 100644 --- a/leakix/async_client.py +++ b/leakix/async_client.py @@ -135,6 +135,10 @@ async def get_plugins(self) -> AbstractResponse: """Returns the list of plugins the authenticated user has access to.""" return self._parse_plugins(await self.__get("/api/plugins")) + async def get_plugin(self, name: str) -> AbstractResponse: + """Returns the description of a plugin by its name.""" + return self._parse_plugin(await self.__get(f"/api/plugins/{name}")) + async def get_subdomains(self, domain: str) -> AbstractResponse: """Returns the list of subdomains for a given domain.""" return self._parse_subdomains(await self.__get(f"/api/subdomains/{domain}")) diff --git a/leakix/base.py b/leakix/base.py index 42a23d1..b8f77c0 100644 --- a/leakix/base.py +++ b/leakix/base.py @@ -66,6 +66,12 @@ def _parse_plugins(response: AbstractResponse) -> AbstractResponse: response.response_json = [APIResult.from_dict(d) for d in response.json()] return response + @staticmethod + def _parse_plugin(response: AbstractResponse) -> AbstractResponse: + if response.is_success(): + response.response_json = APIResult.from_dict(response.json()) + return response + @staticmethod def _parse_subdomains(response: AbstractResponse) -> AbstractResponse: if response.is_success(): diff --git a/leakix/client.py b/leakix/client.py index 95aba3f..235770f 100644 --- a/leakix/client.py +++ b/leakix/client.py @@ -113,6 +113,15 @@ def get_plugins(self) -> AbstractResponse: url = f"{self.base_url}/api/plugins" return self._parse_plugins(self.__get(url, params=None)) + def get_plugin(self, name: str) -> AbstractResponse: + """ + Returns the description of a plugin by its name. + + The output is an `APIResult` object with `name` and `description` fields. + """ + url = f"{self.base_url}/api/plugins/{name}" + return self._parse_plugin(self.__get(url, params=None)) + def get_subdomains(self, domain: str) -> AbstractResponse: """ Returns the list of subdomains for a given domain. diff --git a/tests/test_client.py b/tests/test_client.py index a9c7394..3cd91e3 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -220,6 +220,48 @@ def test_get_plugins_unauthorized(self, client): assert response.status_code() == 401 +class TestGetPlugin: + def test_get_plugin_success(self, client_with_api_key): + res_json = { + "name": "GrafanaOpenPlugin", + "description": "Grafana open instances", + } + with requests_mock.Mocker() as m: + m.get( + f"{client_with_api_key.base_url}/api/plugins/GrafanaOpenPlugin", + json=res_json, + status_code=200, + ) + response = client_with_api_key.get_plugin("GrafanaOpenPlugin") + assert response.is_success() + assert response.json().name == "GrafanaOpenPlugin" + assert response.json().description == "Grafana open instances" + + def test_get_plugin_not_found(self, client): + res_json = {"title": "Not Found", "description": "Plugin not found"} + with requests_mock.Mocker() as m: + m.get( + f"{client.base_url}/api/plugins/NonExistent", + json=res_json, + status_code=404, + ) + response = client.get_plugin("NonExistent") + assert response.is_error() + assert response.status_code() == 404 + + def test_get_plugin_unauthorized(self, client): + res_json = {"error": "unauthorized"} + with requests_mock.Mocker() as m: + m.get( + f"{client.base_url}/api/plugins/GrafanaOpenPlugin", + json=res_json, + status_code=401, + ) + response = client.get_plugin("GrafanaOpenPlugin") + assert response.is_error() + assert response.status_code() == 401 + + class TestGetSubdomains: def test_get_subdomains_success(self, client): res_json = [