diff --git a/docs/ahriman.core.http.rst b/docs/ahriman.core.http.rst
index e2df58a4..15b35d3f 100644
--- a/docs/ahriman.core.http.rst
+++ b/docs/ahriman.core.http.rst
@@ -4,6 +4,14 @@ ahriman.core.http package
Submodules
----------
+ahriman.core.http.sync\_ahriman\_client module
+----------------------------------------------
+
+.. automodule:: ahriman.core.http.sync_ahriman_client
+ :members:
+ :no-undoc-members:
+ :show-inheritance:
+
ahriman.core.http.sync\_http\_client module
-------------------------------------------
diff --git a/src/ahriman/core/http/__init__.py b/src/ahriman/core/http/__init__.py
index 3e476dd1..02b72d30 100644
--- a/src/ahriman/core/http/__init__.py
+++ b/src/ahriman/core/http/__init__.py
@@ -17,4 +17,5 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
+from ahriman.core.http.sync_ahriman_client import SyncAhrimanClient
from ahriman.core.http.sync_http_client import MultipartType, SyncHttpClient
diff --git a/src/ahriman/core/http/sync_ahriman_client.py b/src/ahriman/core/http/sync_ahriman_client.py
new file mode 100644
index 00000000..982d633e
--- /dev/null
+++ b/src/ahriman/core/http/sync_ahriman_client.py
@@ -0,0 +1,85 @@
+#
+# Copyright (c) 2021-2023 ahriman team.
+#
+# This file is part of ahriman
+# (see https://github.com/arcan1s/ahriman).
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+import contextlib
+import requests
+
+from functools import cached_property
+from urllib.parse import urlparse
+
+from ahriman import __version__
+from ahriman.core.http.sync_http_client import SyncHttpClient
+
+
+class SyncAhrimanClient(SyncHttpClient):
+ """
+ wrapper for ahriman web service
+
+ Attributes:
+ address(str): address of the web service
+ """
+
+ address: str
+
+ @cached_property
+ def session(self) -> requests.Session:
+ """
+ get or create session
+
+ Returns:
+ request.Session: created session object
+ """
+ if urlparse(self.address).scheme == "http+unix":
+ import requests_unixsocket # type: ignore[import-untyped]
+ session: requests.Session = requests_unixsocket.Session()
+ session.headers["User-Agent"] = f"ahriman/{__version__}"
+ return session
+
+ session = requests.Session()
+ session.headers["User-Agent"] = f"ahriman/{__version__}"
+ self._login(session)
+
+ return session
+
+ def _login(self, session: requests.Session) -> None:
+ """
+ process login to the service
+
+ Args:
+ session(requests.Session): request session to login
+ """
+ if self.auth is None:
+ return # no auth configured
+
+ username, password = self.auth
+ payload = {
+ "username": username,
+ "password": password,
+ }
+ with contextlib.suppress(Exception):
+ self.make_request("POST", self._login_url(), json=payload, session=session)
+
+ def _login_url(self) -> str:
+ """
+ get url for the login api
+
+ Returns:
+ str: full url for web service to log in
+ """
+ return f"{self.address}/api/v1/login"
diff --git a/src/ahriman/core/status/web_client.py b/src/ahriman/core/status/web_client.py
index a5683c06..0978fdc8 100644
--- a/src/ahriman/core/status/web_client.py
+++ b/src/ahriman/core/status/web_client.py
@@ -19,14 +19,11 @@
#
import contextlib
import logging
-import requests
-from functools import cached_property
-from urllib.parse import quote_plus as urlencode, urlparse
+from urllib.parse import quote_plus as urlencode
-from ahriman import __version__
from ahriman.core.configuration import Configuration
-from ahriman.core.http import SyncHttpClient
+from ahriman.core.http import SyncAhrimanClient
from ahriman.core.status.client import Client
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.internal_status import InternalStatus
@@ -35,12 +32,11 @@ from ahriman.models.package import Package
from ahriman.models.repository_id import RepositoryId
-class WebClient(Client, SyncHttpClient):
+class WebClient(Client, SyncAhrimanClient):
"""
build status reporter web client
Attributes:
- address(str): address of the web service
repository_id(RepositoryId): repository unique identifier
"""
@@ -56,30 +52,10 @@ class WebClient(Client, SyncHttpClient):
suppress_errors = configuration.getboolean( # read old-style first and then fallback to new style
"settings", "suppress_http_log_errors",
fallback=configuration.getboolean("status", "suppress_http_log_errors", fallback=False))
- SyncHttpClient.__init__(self, configuration, section, suppress_errors=suppress_errors)
+ SyncAhrimanClient.__init__(self, configuration, section, suppress_errors=suppress_errors)
self.repository_id = repository_id
- @cached_property
- def session(self) -> requests.Session:
- """
- get or create session
-
- Returns:
- request.Session: created session object
- """
- if urlparse(self.address).scheme == "http+unix":
- import requests_unixsocket # type: ignore[import-untyped]
- session: requests.Session = requests_unixsocket.Session()
- session.headers["User-Agent"] = f"ahriman/{__version__}"
- return session
-
- session = requests.Session()
- session.headers["User-Agent"] = f"ahriman/{__version__}"
- self._login(session)
-
- return session
-
@staticmethod
def parse_address(configuration: Configuration) -> tuple[str, str]:
"""
@@ -107,33 +83,6 @@ class WebClient(Client, SyncHttpClient):
address = f"http://{host}:{port}"
return "web", address
- def _login(self, session: requests.Session) -> None:
- """
- process login to the service
-
- Args:
- session(requests.Session): request session to login
- """
- if self.auth is None:
- return # no auth configured
-
- username, password = self.auth
- payload = {
- "username": username,
- "password": password,
- }
- with contextlib.suppress(Exception):
- self.make_request("POST", self._login_url(), json=payload, session=session)
-
- def _login_url(self) -> str:
- """
- get url for the login api
-
- Returns:
- str: full url for web service to log in
- """
- return f"{self.address}/api/v1/login"
-
def _logs_url(self, package_base: str) -> str:
"""
get url for the logs api
diff --git a/tests/ahriman/core/http/conftest.py b/tests/ahriman/core/http/conftest.py
new file mode 100644
index 00000000..16ef43df
--- /dev/null
+++ b/tests/ahriman/core/http/conftest.py
@@ -0,0 +1,21 @@
+import pytest
+
+from ahriman.core.configuration import Configuration
+from ahriman.core.http import SyncAhrimanClient
+from ahriman.core.status.web_client import WebClient
+
+
+@pytest.fixture
+def ahriman_client(configuration: Configuration) -> SyncAhrimanClient:
+ """
+ ahriman web client fixture
+
+ Args:
+ configuration(Configuration): configuration fixture
+
+ Returns:
+ SyncAhrimanClient: ahriman web client test instance
+ """
+ configuration.set("web", "port", "8080")
+ _, repository_id = configuration.check_loaded()
+ return WebClient(repository_id, configuration)
diff --git a/tests/ahriman/core/http/test_sync_ahriman_client.py b/tests/ahriman/core/http/test_sync_ahriman_client.py
new file mode 100644
index 00000000..2a83bcac
--- /dev/null
+++ b/tests/ahriman/core/http/test_sync_ahriman_client.py
@@ -0,0 +1,81 @@
+import pytest
+import requests
+import requests_unixsocket
+
+from pytest_mock import MockerFixture
+
+from ahriman.core.http import SyncAhrimanClient
+from ahriman.models.user import User
+
+
+def test_session(ahriman_client: SyncAhrimanClient, mocker: MockerFixture) -> None:
+ """
+ must create normal requests session
+ """
+ login_mock = mocker.patch("ahriman.core.http.SyncAhrimanClient._login")
+
+ assert isinstance(ahriman_client.session, requests.Session)
+ assert not isinstance(ahriman_client.session, requests_unixsocket.Session)
+ login_mock.assert_called_once_with(pytest.helpers.anyvar(int))
+
+
+def test_session_unix_socket(ahriman_client: SyncAhrimanClient, mocker: MockerFixture) -> None:
+ """
+ must create unix socket session
+ """
+ login_mock = mocker.patch("ahriman.core.http.SyncAhrimanClient._login")
+ ahriman_client.address = "http+unix://path"
+
+ assert isinstance(ahriman_client.session, requests_unixsocket.Session)
+ login_mock.assert_not_called()
+
+
+def test_login(ahriman_client: SyncAhrimanClient, user: User, mocker: MockerFixture) -> None:
+ """
+ must login user
+ """
+ ahriman_client.auth = (user.username, user.password)
+ requests_mock = mocker.patch("ahriman.core.http.SyncAhrimanClient.make_request")
+ payload = {
+ "username": user.username,
+ "password": user.password
+ }
+ session = requests.Session()
+
+ ahriman_client._login(session)
+ requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), json=payload, session=session)
+
+
+def test_login_failed(ahriman_client: SyncAhrimanClient, user: User, mocker: MockerFixture) -> None:
+ """
+ must suppress any exception happened during login
+ """
+ ahriman_client.user = user
+ mocker.patch("requests.Session.request", side_effect=Exception())
+ ahriman_client._login(requests.Session())
+
+
+def test_login_failed_http_error(ahriman_client: SyncAhrimanClient, user: User, mocker: MockerFixture) -> None:
+ """
+ must suppress HTTP exception happened during login
+ """
+ ahriman_client.user = user
+ mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
+ ahriman_client._login(requests.Session())
+
+
+def test_login_skip(ahriman_client: SyncAhrimanClient, mocker: MockerFixture) -> None:
+ """
+ must skip login if no user set
+ """
+ requests_mock = mocker.patch("requests.Session.request")
+ ahriman_client._login(requests.Session())
+ requests_mock.assert_not_called()
+
+
+def test_login_url(ahriman_client: SyncAhrimanClient) -> None:
+ """
+ must generate login url correctly
+ """
+ assert ahriman_client._login_url().startswith(ahriman_client.address)
+ assert ahriman_client._login_url().endswith("/api/v1/login")
diff --git a/tests/ahriman/core/status/test_web_client.py b/tests/ahriman/core/status/test_web_client.py
index 0c5fcdff..08cb0f4f 100644
--- a/tests/ahriman/core/status/test_web_client.py
+++ b/tests/ahriman/core/status/test_web_client.py
@@ -2,7 +2,6 @@ import json
import logging
import pytest
import requests
-import requests_unixsocket
from pytest_mock import MockerFixture
@@ -12,29 +11,6 @@ from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.internal_status import InternalStatus
from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package
-from ahriman.models.user import User
-
-
-def test_session(web_client: WebClient, mocker: MockerFixture) -> None:
- """
- must create normal requests session
- """
- login_mock = mocker.patch("ahriman.core.status.web_client.WebClient._login")
-
- assert isinstance(web_client.session, requests.Session)
- assert not isinstance(web_client.session, requests_unixsocket.Session)
- login_mock.assert_called_once_with(pytest.helpers.anyvar(int))
-
-
-def test_session_unix_socket(web_client: WebClient, mocker: MockerFixture) -> None:
- """
- must create unix socket session
- """
- login_mock = mocker.patch("ahriman.core.status.web_client.WebClient._login")
- web_client.address = "http+unix://path"
-
- assert isinstance(web_client.session, requests_unixsocket.Session)
- login_mock.assert_not_called()
def test_parse_address(configuration: Configuration) -> None:
@@ -55,57 +31,6 @@ def test_parse_address(configuration: Configuration) -> None:
assert WebClient.parse_address(configuration) == ("status", "http://localhost:8082")
-def test_login(web_client: WebClient, user: User, mocker: MockerFixture) -> None:
- """
- must login user
- """
- web_client.auth = (user.username, user.password)
- requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request")
- payload = {
- "username": user.username,
- "password": user.password
- }
- session = requests.Session()
-
- web_client._login(session)
- requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), json=payload, session=session)
-
-
-def test_login_failed(web_client: WebClient, user: User, mocker: MockerFixture) -> None:
- """
- must suppress any exception happened during login
- """
- web_client.user = user
- mocker.patch("requests.Session.request", side_effect=Exception())
- web_client._login(requests.Session())
-
-
-def test_login_failed_http_error(web_client: WebClient, user: User, mocker: MockerFixture) -> None:
- """
- must suppress HTTP exception happened during login
- """
- web_client.user = user
- mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
- web_client._login(requests.Session())
-
-
-def test_login_skip(web_client: WebClient, mocker: MockerFixture) -> None:
- """
- must skip login if no user set
- """
- requests_mock = mocker.patch("requests.Session.request")
- web_client._login(requests.Session())
- requests_mock.assert_not_called()
-
-
-def test_login_url(web_client: WebClient) -> None:
- """
- must generate login url correctly
- """
- assert web_client._login_url().startswith(web_client.address)
- assert web_client._login_url().endswith("/api/v1/login")
-
-
def test_status_url(web_client: WebClient) -> None:
"""
must generate package status url correctly
@@ -377,11 +302,13 @@ def test_status_update(web_client: WebClient, mocker: MockerFixture) -> None:
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request")
web_client.status_update(BuildStatusEnum.Unknown)
- requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True),
- params=web_client.repository_id.query(),
- json={
- "status": BuildStatusEnum.Unknown.value,
- })
+ requests_mock.assert_called_once_with(
+ "POST", pytest.helpers.anyvar(str, True),
+ params=web_client.repository_id.query(),
+ json={
+ "status": BuildStatusEnum.Unknown.value,
+ }
+ )
def test_status_update_self_failed(web_client: WebClient, mocker: MockerFixture) -> None: