From fa4f5cb14e8aae070b37914294998a593123ec1b Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Sat, 22 Mar 2025 19:59:24 +0000
Subject: [PATCH 01/23] Remove notifications

---
 .gitlab-ci.yml                                |  16 +-
 .../abc/http_server_observer.py               |  17 --
 .../abc/notification_handler.py               |  21 --
 .../appbase_notification_handler.py           |  40 ---
 .../beekeepy/_communication/async_server.py   | 104 -------
 .../_communication/notification_decorator.py  |  43 ---
 .../universal_notification_server.py          |  87 ------
 .../beekeepy/_executable/beekeeper_config.py  |   1 -
 .../_executable/beekeeper_executable.py       |   5 +-
 beekeepy/beekeepy/_executable/defaults.py     |   1 -
 beekeepy/beekeepy/_remote_handle/settings.py  |  14 +-
 .../beekeepy/_runnable_handle/beekeeper.py    | 248 ++++-------------
 .../_runnable_handle/beekeeper_callbacks.py   |  42 ---
 .../beekeeper_notification_handler.py         |  70 -----
 .../beekeepy/_runnable_handle/match_ports.py  |  72 +++++
 .../notification_handler_base.py              |  29 --
 .../_runnable_handle/runnable_handle.py       | 256 ++++++++++++++++++
 .../beekeepy/_runnable_handle/settings.py     |  13 +-
 beekeepy/beekeepy/exceptions/__init__.py      |  12 +-
 beekeepy/beekeepy/exceptions/base.py          |   4 +
 beekeepy/beekeepy/exceptions/executable.py    |  23 ++
 beekeepy/poetry.lock                          | 193 +++++++------
 beekeepy/pyproject.toml                       |   8 +-
 hive                                          |   2 +-
 .../api_tests/test_api_close_session.py       |   6 +-
 .../api_tests/test_api_create_session.py      |   8 +-
 .../test_default_values.py                    |   1 -
 .../test_notifications_endpoint.py            |  37 ---
 .../test_webserver_http_endpoint.py           |  13 +-
 .../handle/various/test_blocking_unlock.py    |   8 +-
 tests/local-tools/poetry.lock                 | 210 ++++++++------
 tests/local-tools/pyproject.toml              |   1 +
 32 files changed, 678 insertions(+), 927 deletions(-)
 delete mode 100644 beekeepy/beekeepy/_communication/abc/http_server_observer.py
 delete mode 100644 beekeepy/beekeepy/_communication/abc/notification_handler.py
 delete mode 100644 beekeepy/beekeepy/_communication/appbase_notification_handler.py
 delete mode 100644 beekeepy/beekeepy/_communication/async_server.py
 delete mode 100644 beekeepy/beekeepy/_communication/notification_decorator.py
 delete mode 100644 beekeepy/beekeepy/_communication/universal_notification_server.py
 delete mode 100644 beekeepy/beekeepy/_runnable_handle/beekeeper_callbacks.py
 delete mode 100644 beekeepy/beekeepy/_runnable_handle/beekeeper_notification_handler.py
 create mode 100644 beekeepy/beekeepy/_runnable_handle/match_ports.py
 delete mode 100644 beekeepy/beekeepy/_runnable_handle/notification_handler_base.py
 create mode 100644 beekeepy/beekeepy/_runnable_handle/runnable_handle.py
 create mode 100644 beekeepy/beekeepy/exceptions/executable.py
 delete mode 100644 tests/beekeepy_test/handle/commandline/application_options/test_notifications_endpoint.py

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index df788639..666fc2b8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -34,7 +34,7 @@ variables:
 include:
   - project: 'hive/hive'
     # This has to be the same as the commit checked out in the submodule
-    ref: 18f1d5c753735ddba3ab85baaffc09283d84c652
+    ref: 40e6bc384b63fe116f370153a20c15a46888011f
     file: '/scripts/ci-helpers/prepare_data_image_job.yml'
   # DO NOT include ccc here. It will be indirectly included by above yaml file.
   #- project: 'hive/common-ci-configuration'
@@ -96,20 +96,6 @@ prepare_hived_image:
     - public-runner-docker
     - hived-for-tests
 
-prepare_hived_data:
-  extends: .prepare_hived_data_5m
-  needs:
-    - job: prepare_hived_image
-      artifacts: true
-  stage: build
-  variables:
-    GIT_SUBMODULE_STRATEGY: normal
-    SUBMODULE_DIR: "$CI_PROJECT_DIR/hive"
-    BLOCK_LOG_SOURCE_DIR: $BLOCK_LOG_SOURCE_DIR_5M
-    CONFIG_INI_SOURCE: "$CI_PROJECT_DIR/hive/docker/config_5M.ini"
-  tags:
-    - data-cache-storage
-
 build_beekeepy_wheel:
   stage: beekeepy
   extends: .build_wheel_template
diff --git a/beekeepy/beekeepy/_communication/abc/http_server_observer.py b/beekeepy/beekeepy/_communication/abc/http_server_observer.py
deleted file mode 100644
index 6d4efcee..00000000
--- a/beekeepy/beekeepy/_communication/abc/http_server_observer.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from __future__ import annotations
-
-from abc import ABC, abstractmethod
-from typing import Any
-
-
-class HttpServerObserver(ABC):
-    @abstractmethod
-    async def data_received(self, data: dict[str, Any]) -> None:
-        """Called when any data is received via PUT method.
-
-        Args:
-            data: data received as body
-
-        Returns:
-            Nothing.
-        """
diff --git a/beekeepy/beekeepy/_communication/abc/notification_handler.py b/beekeepy/beekeepy/_communication/abc/notification_handler.py
deleted file mode 100644
index da69e8b5..00000000
--- a/beekeepy/beekeepy/_communication/abc/notification_handler.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from __future__ import annotations
-
-from abc import ABC, abstractmethod
-from typing import Any
-
-from beekeepy._communication.abc.http_server_observer import HttpServerObserver
-from schemas.notifications import KnownNotificationT, Notification
-
-
-class NotificationHandler(HttpServerObserver, ABC):
-    async def data_received(self, data: dict[str, Any]) -> None:
-        deserialized_notification = Notification(**data)
-        await self.handle_notification(deserialized_notification)
-
-    @abstractmethod
-    async def handle_notification(self, notification: Notification[KnownNotificationT]) -> None:
-        """Method called after properly serializing notification.
-
-        Args:
-            notification: received notification object
-        """
diff --git a/beekeepy/beekeepy/_communication/appbase_notification_handler.py b/beekeepy/beekeepy/_communication/appbase_notification_handler.py
deleted file mode 100644
index c9d0241a..00000000
--- a/beekeepy/beekeepy/_communication/appbase_notification_handler.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-from beekeepy._communication.notification_decorator import notification
-from beekeepy._communication.universal_notification_server import UniversalNotificationHandler
-from schemas.notifications import Error, Status, WebserverListening
-
-if TYPE_CHECKING:
-    from schemas.notifications import Notification
-
-
-class AppbaseNotificationHandler(UniversalNotificationHandler):
-    @notification(WebserverListening, condition=lambda n: n.value.type_ == "HTTP")
-    async def _on_http_webserver_bind(self, notification: Notification[WebserverListening]) -> None:
-        await self.on_http_webserver_bind(notification)
-
-    @notification(WebserverListening, condition=lambda n: n.value.type_ == "WS")
-    async def _on_ws_webserver_bind(self, notification: Notification[WebserverListening]) -> None:
-        await self.on_ws_webserver_bind(notification)
-
-    @notification(Status)
-    async def _on_status_changed(self, notification: Notification[Status]) -> None:
-        await self.on_status_changed(notification)
-
-    @notification(Error)
-    async def _on_error(self, notification: Notification[Error]) -> None:
-        await self.on_error(notification)
-
-    async def on_http_webserver_bind(self, notification: Notification[WebserverListening]) -> None:
-        """Called when hived reports http server to be ready."""
-
-    async def on_ws_webserver_bind(self, notification: Notification[WebserverListening]) -> None:
-        """Called when hived reports ws server to be ready."""
-
-    async def on_status_changed(self, notification: Notification[Status]) -> None:
-        """Called when status of notifier changed."""
-
-    async def on_error(self, notification: Notification[Error]) -> None:
-        """Called when notifier reports an error."""
diff --git a/beekeepy/beekeepy/_communication/async_server.py b/beekeepy/beekeepy/_communication/async_server.py
deleted file mode 100644
index 75501017..00000000
--- a/beekeepy/beekeepy/_communication/async_server.py
+++ /dev/null
@@ -1,104 +0,0 @@
-from __future__ import annotations
-
-import asyncio
-from http import HTTPStatus
-from typing import TYPE_CHECKING
-
-from aiohttp import web
-from typing_extensions import Self
-
-from beekeepy._interface.context import ContextAsync
-from beekeepy._interface.url import HttpUrl
-from beekeepy.exceptions import BeekeepyError
-
-if TYPE_CHECKING:
-    from socket import socket
-
-    from beekeepy._communication.abc.http_server_observer import HttpServerObserver
-
-
-class AsyncHttpServerError(BeekeepyError):
-    pass
-
-
-class ServerNotRunningError(AsyncHttpServerError):
-    def __init__(self) -> None:
-        super().__init__("Server is not running. Call run() first.")
-
-
-class ServerAlreadyRunningError(AsyncHttpServerError):
-    def __init__(self) -> None:
-        super().__init__("Server is already running. Call close() first.")
-
-
-class ServerSetupError(AsyncHttpServerError):
-    def __init__(self, message: str) -> None:
-        super().__init__(message)
-
-
-class AsyncHttpServer(ContextAsync[Self]):  # type: ignore[misc]
-    __ADDRESS = HttpUrl("0.0.0.0:0")
-
-    def __init__(self, observer: HttpServerObserver, notification_endpoint: HttpUrl | None) -> None:
-        self.__observer = observer
-        self._app = web.Application()
-        self.__site: web.TCPSite | None = None
-        self.__running: bool = False
-        self.__notification_endpoint = notification_endpoint
-        self._setup_routes()
-
-    def _setup_routes(self) -> None:
-        async def handle_put_method(request: web.Request) -> web.Response:
-            await self.__observer.data_received(await request.json())
-            return web.Response(status=HTTPStatus.NO_CONTENT)
-
-        self._app.router.add_route("PUT", "/", handle_put_method)
-
-    @property
-    def port(self) -> int:
-        if not self.__site:
-            raise ServerNotRunningError
-        server: asyncio.base_events.Server | None = self.__site._server  # type: ignore[assignment]
-        if server is None:
-            raise ServerSetupError("self.__site.server is None")
-
-        server_socket: socket = server.sockets[0]
-        address_tuple: tuple[str, int] = server_socket.getsockname()
-
-        if not (
-            isinstance(address_tuple, tuple) and isinstance(address_tuple[0], str) and isinstance(address_tuple[1], int)
-        ):
-            raise ServerSetupError(f"address_tuple has not recognizable types: {address_tuple}")
-
-        return address_tuple[1]
-
-    async def run(self) -> None:
-        if self.__site:
-            raise ServerAlreadyRunningError
-
-        time_between_checks_is_server_running = 0.5
-
-        runner = web.AppRunner(self._app, access_log=False)
-        await runner.setup()
-        address = self.__notification_endpoint or self.__ADDRESS
-        self.__site = web.TCPSite(runner, address.address, address.port)
-        await self.__site.start()
-        self.__running = True
-        try:
-            while self.__running:  # noqa: ASYNC110
-                await asyncio.sleep(time_between_checks_is_server_running)
-        finally:
-            await self.__site.stop()
-            self.__site = None
-
-    def close(self) -> None:
-        if not self.__site:
-            raise ServerNotRunningError
-        self.__running = False
-
-    async def _aenter(self) -> Self:
-        await self.run()
-        return self
-
-    async def _afinally(self) -> None:
-        self.close()
diff --git a/beekeepy/beekeepy/_communication/notification_decorator.py b/beekeepy/beekeepy/_communication/notification_decorator.py
deleted file mode 100644
index caa22a44..00000000
--- a/beekeepy/beekeepy/_communication/notification_decorator.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from __future__ import annotations
-
-from collections.abc import Awaitable, Callable  # noqa: TCH003
-from typing import Any, Generic
-
-from pydantic.generics import GenericModel
-
-from schemas.notifications import KnownNotificationT, Notification
-
-
-class _NotificationHandlerWrapper(GenericModel, Generic[KnownNotificationT]):
-    notification_name: str
-    notification_handler: Callable[[Any, Notification[KnownNotificationT]], Awaitable[None]]
-    notification_condition: Callable[[Notification[KnownNotificationT]], bool]
-
-    async def call(self, this: Any, notification: Notification[KnownNotificationT]) -> None:
-        await self.notification_handler(this, notification)
-
-    async def __call__(self, this: Any, notification: Notification[KnownNotificationT]) -> Any:
-        return self.call(this, notification)
-
-
-def notification(
-    type_: type[KnownNotificationT],
-    /,
-    *,
-    condition: Callable[[Notification[KnownNotificationT]], bool] | None = None,
-) -> Callable[
-    [Callable[[Any, Notification[KnownNotificationT]], Awaitable[None]]],
-    _NotificationHandlerWrapper[KnownNotificationT],
-]:
-    def wrapper(
-        callback: Callable[[Any, Notification[KnownNotificationT]], Awaitable[None]],
-    ) -> _NotificationHandlerWrapper[KnownNotificationT]:
-        result_cls = _NotificationHandlerWrapper[type_]  # type: ignore[valid-type]
-        result_cls.update_forward_refs(**locals())
-        return result_cls(  # type: ignore[return-value]
-            notification_name=type_.get_notification_name(),
-            notification_handler=callback,
-            notification_condition=condition or (lambda _: True),
-        )
-
-    return wrapper
diff --git a/beekeepy/beekeepy/_communication/universal_notification_server.py b/beekeepy/beekeepy/_communication/universal_notification_server.py
deleted file mode 100644
index 7813e49b..00000000
--- a/beekeepy/beekeepy/_communication/universal_notification_server.py
+++ /dev/null
@@ -1,87 +0,0 @@
-from __future__ import annotations
-
-import asyncio
-from collections import defaultdict
-from threading import Thread
-from time import sleep
-from typing import TYPE_CHECKING, Any, Final
-
-from beekeepy._communication.abc.notification_handler import NotificationHandler
-from beekeepy._communication.async_server import AsyncHttpServer
-from beekeepy._communication.notification_decorator import _NotificationHandlerWrapper
-from beekeepy._interface.context import ContextSync
-from beekeepy.exceptions import BeekeepyError
-
-if TYPE_CHECKING:
-    from beekeepy._interface.url import HttpUrl
-    from schemas.notifications import KnownNotificationT, Notification
-
-
-class UnhandledNotificationError(BeekeepyError):
-    def __init__(self, notification: Notification[KnownNotificationT]) -> None:
-        super().__init__(
-            f"Notification `{notification.name}` does not have any registered method to be passed to.", notification
-        )
-
-
-class UniversalNotificationHandler(NotificationHandler):
-    def __init__(self, *args: Any, **kwargs: Any) -> None:
-        self.__registered_notifications: defaultdict[str, list[_NotificationHandlerWrapper[Any]]] = defaultdict(list)
-        super().__init__(*args, **kwargs)
-
-    def setup(self) -> None:
-        for member_name in dir(self):
-            member_value = getattr(self, member_name)
-            if isinstance(member_value, _NotificationHandlerWrapper):
-                self.__registered_notifications[member_value.notification_name].append(member_value)
-
-    async def handle_notification(self, notification: Notification[KnownNotificationT]) -> None:
-        if (callbacks := self.__registered_notifications.get(notification.name)) is not None:
-            for callback in callbacks:
-                if callback.notification_condition(notification):
-                    await callback.call(self, notification)
-                    return
-
-        raise UnhandledNotificationError(notification)
-
-
-class UniversalNotificationServer(ContextSync[int]):
-    def __init__(
-        self,
-        event_handler: UniversalNotificationHandler,
-        notification_endpoint: HttpUrl | None = None,
-        *,
-        thread_name: str | None = None,
-    ) -> None:
-        self.__event_handler = event_handler
-        self.__event_handler.setup()
-        self.__http_server = AsyncHttpServer(self.__event_handler, notification_endpoint=notification_endpoint)
-        self.__server_thread: Thread | None = None
-        self.__thread_name = thread_name
-
-    def run(self) -> int:
-        time_to_launch_notification_server: Final[float] = 0.5
-        assert self.__server_thread is None, "Server thread is not None; Is server still running?"
-
-        self.__server_thread = Thread(target=asyncio.run, args=(self.__http_server.run(),), name=self.__thread_name)
-        self.__server_thread.start()
-        sleep(time_to_launch_notification_server)
-        return self.__http_server.port
-
-    def close(self) -> None:
-        if self.__server_thread is None:
-            return
-
-        self.__http_server.close()
-        self.__server_thread.join()
-        self.__server_thread = None
-
-    @property
-    def port(self) -> int:
-        return self.__http_server.port
-
-    def _enter(self) -> int:
-        return self.run()
-
-    def _finally(self) -> None:
-        self.close()
diff --git a/beekeepy/beekeepy/_executable/beekeeper_config.py b/beekeepy/beekeepy/_executable/beekeeper_config.py
index 904ebb7c..3aea8726 100644
--- a/beekeepy/beekeepy/_executable/beekeeper_config.py
+++ b/beekeepy/beekeepy/_executable/beekeeper_config.py
@@ -23,7 +23,6 @@ class BeekeeperConfig(Config):
     webserver_ws_endpoint: WsUrl | None = None
     webserver_ws_deflate: int = 0
     webserver_thread_pool_size: int = 1
-    notifications_endpoint: HttpUrl | None = BeekeeperDefaults.DEFAULT_NOTIFICATIONS_ENDPOINT
     backtrace: str = BeekeeperDefaults.DEFAULT_BACKTRACE
     plugin: list[str] = Field(default_factory=lambda: ["json_rpc", "webserver"])
     export_keys_wallet: ExportKeysWalletParams | None = BeekeeperDefaults.DEFAULT_EXPORT_KEYS_WALLET
diff --git a/beekeepy/beekeepy/_executable/beekeeper_executable.py b/beekeepy/beekeepy/_executable/beekeeper_executable.py
index 38ebcf1b..39e94765 100644
--- a/beekeepy/beekeepy/_executable/beekeeper_executable.py
+++ b/beekeepy/beekeepy/_executable/beekeeper_executable.py
@@ -25,7 +25,9 @@ class BeekeeperExecutable(Executable[BeekeeperConfig, BeekeeperArguments]):
         )
 
     def _construct_config(self) -> BeekeeperConfig:
-        return BeekeeperConfig(wallet_dir=self.working_directory)
+        config = BeekeeperConfig(wallet_dir=self.working_directory)
+        config.plugin.append("app_status_api")
+        return config
 
     def _construct_arguments(self) -> BeekeeperArguments:
         return BeekeeperArguments(data_dir=self.working_directory)
@@ -45,7 +47,6 @@ class BeekeeperExecutable(Executable[BeekeeperConfig, BeekeeperArguments]):
                 blocking=True,
                 arguments=BeekeeperArguments(
                     data_dir=tempdir_path,
-                    notifications_endpoint=HttpUrl("0.0.0.0:0"),
                     export_keys_wallet=ExportKeysWalletParams(wallet_name=wallet_name, wallet_password=wallet_password),
                 ),
             )
diff --git a/beekeepy/beekeepy/_executable/defaults.py b/beekeepy/beekeepy/_executable/defaults.py
index c3f2f5d5..955fcf0a 100644
--- a/beekeepy/beekeepy/_executable/defaults.py
+++ b/beekeepy/beekeepy/_executable/defaults.py
@@ -20,7 +20,6 @@ class BeekeeperDefaults(BaseModel):
     DEFAULT_DATA_DIR: ClassVar[Path] = Path.cwd()
     DEFAULT_EXPORT_KEYS_WALLET: ClassVar[ExportKeysWalletParams | None] = None
     DEFAULT_LOG_JSON_RPC: ClassVar[Path | None] = None
-    DEFAULT_NOTIFICATIONS_ENDPOINT: ClassVar[HttpUrl | None] = None
     DEFAULT_UNLOCK_TIMEOUT: ClassVar[int] = 900
     DEFAULT_UNLOCK_INTERVAL: ClassVar[int] = 500
     DEFAULT_WALLET_DIR: ClassVar[Path] = Path.cwd()
diff --git a/beekeepy/beekeepy/_remote_handle/settings.py b/beekeepy/beekeepy/_remote_handle/settings.py
index ede77a6a..470e6c1a 100644
--- a/beekeepy/beekeepy/_remote_handle/settings.py
+++ b/beekeepy/beekeepy/_remote_handle/settings.py
@@ -2,14 +2,16 @@ from __future__ import annotations
 
 from typing import ClassVar
 
-from beekeepy._communication.abc.communicator import AbstractCommunicator  # noqa: TCH001
-from beekeepy._communication.abc.overseer import AbstractOverseer
-from beekeepy._communication.overseers import CommonOverseer
-from beekeepy._communication.settings import CommunicationSettings
-from beekeepy._interface.url import HttpUrl  # noqa: TCH001
+from beekeepy._communication import (
+    AbstractCommunicator,
+    AbstractOverseer,
+    CommonOverseer,
+    CommunicationSettings,
+    HttpUrl,
+)
 
 
-class Settings(CommunicationSettings):
+class RemoteHandleSettings(CommunicationSettings):
     class Defaults(CommunicationSettings.Defaults):
         OVERSEER: ClassVar[type[AbstractOverseer]] = CommonOverseer
 
diff --git a/beekeepy/beekeepy/_runnable_handle/beekeeper.py b/beekeepy/beekeepy/_runnable_handle/beekeeper.py
index 58bc9f66..912ca3df 100644
--- a/beekeepy/beekeepy/_runnable_handle/beekeeper.py
+++ b/beekeepy/beekeepy/_runnable_handle/beekeeper.py
@@ -1,250 +1,114 @@
 from __future__ import annotations
 
-from abc import ABC, abstractmethod
-from subprocess import CalledProcessError
-from typing import TYPE_CHECKING, Any, TypeVar, cast
-
-from beekeepy._communication.universal_notification_server import (
-    UniversalNotificationServer,
-)
-from beekeepy._executable import BeekeeperArguments, BeekeeperExecutable
-from beekeepy._executable.arguments.beekeeper_arguments import (
-    BeekeeperArgumentsDefaults,
-)
-from beekeepy._interface.url import HttpUrl
-from beekeepy._remote_handle import beekeeper as remote_beekeeper
-from beekeepy._runnable_handle.beekeeper_callbacks import BeekeeperNotificationCallbacks
-from beekeepy._runnable_handle.beekeeper_notification_handler import NotificationHandler
+from typing import TYPE_CHECKING, TypeVar
+
+from beekeepy._executable import BeekeeperArguments, BeekeeperConfig, BeekeeperExecutable
+from beekeepy._remote_handle import AsyncBeekeeperTemplate, BeekeeperTemplate
+from beekeepy._runnable_handle.runnable_handle import RunnableHandle
 from beekeepy._runnable_handle.settings import Settings
-from beekeepy.exceptions import (
-    BeekeeperFailedToStartDuringProcessSpawnError,
-    BeekeeperFailedToStartNotReadyOnTimeError,
-    BeekeeperIsNotRunningError,
-)
+from beekeepy.exceptions import BeekeeperFailedToStartError, ExecutableError
 
 if TYPE_CHECKING:
     from pathlib import Path
 
-    from loguru import Logger
-
-    from beekeepy._executable.beekeeper_config import BeekeeperConfig
-    from beekeepy._interface.key_pair import KeyPair
-    from schemas.notifications import (
-        Error,
-        Notification,
-        Status,
-        WebserverListening,
-    )
-
-
-EnterReturnT = TypeVar("EnterReturnT", bound=remote_beekeeper.Beekeeper | remote_beekeeper.AsyncBeekeeper)
+    from beekeepy._communication import HttpUrl
+    from beekeepy._executable import KeyPair
+    from beekeepy._runnable_handle.match_ports import PortMatchingResult
 
 
 __all__ = [
-    "SyncRemoteBeekeeper",
-    "AsyncRemoteBeekeeper",
     "Beekeeper",
     "AsyncBeekeeper",
 ]
 
+RunnableSettingsT = TypeVar("RunnableSettingsT", bound=Settings)
 
-class SyncRemoteBeekeeper(remote_beekeeper.Beekeeper):
-    pass
-
-
-class AsyncRemoteBeekeeper(remote_beekeeper.AsyncBeekeeper):
-    pass
-
-
-class BeekeeperCommon(BeekeeperNotificationCallbacks, ABC):
-    def __init__(self, *args: Any, settings: Settings, logger: Logger, **kwargs: Any) -> None:
-        super().__init__(*args, settings=settings, logger=logger, **kwargs)
-        self.__exec = BeekeeperExecutable(settings, logger)
-        self.__notification_server: UniversalNotificationServer | None = None
-        self.__notification_event_handler: NotificationHandler | None = None
-        self.__logger = logger
 
-    @property
-    def pid(self) -> int:
-        if not self.is_running:
-            raise BeekeeperIsNotRunningError
-        return self.__exec.pid
+class RunnableBeekeeper(RunnableHandle[BeekeeperExecutable, BeekeeperConfig, BeekeeperArguments, Settings]):
+    def _construct_executable(self) -> BeekeeperExecutable:
+        settings = self._get_settings()
+        return BeekeeperExecutable(
+            executable_path=settings.binary_path,
+            working_directory=settings.ensured_working_directory,
+            logger=self._logger,
+        )
 
-    @property
-    def notification_endpoint(self) -> HttpUrl:
-        endpoint = self._get_settings().notification_endpoint
-        assert endpoint is not None, "Notification endpoint is not set"
-        return endpoint
+    def _get_working_directory_from_cli_arguments(self) -> Path | None:
+        return self.arguments.data_dir
 
-    @property
-    def config(self) -> BeekeeperConfig:
-        return self.__exec.config
+    def _get_http_endpoint_from_cli_arguments(self) -> HttpUrl | None:
+        return self.arguments.webserver_http_endpoint
 
-    @property
-    def is_running(self) -> bool:
-        return self.__exec is not None and self.__exec.is_running()
+    def _get_http_endpoint_from_config(self) -> HttpUrl | None:
+        return self.config.webserver_http_endpoint
 
-    def __setup_notification_server(self, *, address_from_cli_arguments: HttpUrl | None = None) -> None:
-        assert self.__notification_server is None, "Notification server already exists, previous hasn't been close?"
-        assert (
-            self.__notification_event_handler is None
-        ), "Notification event handler already exists, previous hasn't been close?"
+    def _unify_cli_arguments(self, working_directory: Path, http_endpoint: HttpUrl) -> None:
+        self.arguments.data_dir = working_directory
+        self.arguments.webserver_http_endpoint = http_endpoint
 
-        self.__notification_event_handler = NotificationHandler(self)
-        self.__notification_server = UniversalNotificationServer(
-            self.__notification_event_handler,
-            notification_endpoint=address_from_cli_arguments
-            or self._get_settings().notification_endpoint,  # this has to be accessed directly from settings
-        )
+    def _unify_config(self, working_directory: Path, http_endpoint: HttpUrl) -> None:  # noqa: ARG002
+        self.config.webserver_http_endpoint = http_endpoint
 
-    def __close_notification_server(self) -> None:
-        if self.__notification_server is not None:
-            self.__notification_server.close()
-            self.__notification_server = None
-
-        if self.__notification_event_handler is not None:
-            self.__notification_event_handler = None
-
-    def __wait_till_ready(self) -> None:
-        assert self.__notification_event_handler is not None, "Notification event handler hasn't been set"
-        if not self.__notification_event_handler.http_listening_event.wait(
-            timeout=self._get_settings().initialization_timeout.total_seconds()
-        ):
-            raise TimeoutError("Waiting too long for beekeeper to be up and running")
-
-    def _handle_error(self, error: Error) -> None:
-        self.__logger.error(f"Beekeepr error: `{error.json()}`")
-
-    def _handle_status_change(self, status: Status) -> None:
-        self.__logger.info(f"Beekeeper status change to: `{status.current_status}`")
-
-    def _run(
-        self,
-        settings: Settings,
-        additional_cli_arguments: BeekeeperArguments | None = None,
-    ) -> None:
-        aca = additional_cli_arguments or BeekeeperArguments()
-        self.__setup_notification_server(address_from_cli_arguments=aca.notifications_endpoint)
-        assert self.__notification_server is not None, "Creation of notification server failed"
-        settings.notification_endpoint = HttpUrl(f"127.0.0.1:{self.__notification_server.run()}", protocol="http")
-        settings.http_endpoint = (
-            aca.webserver_http_endpoint or settings.http_endpoint or HttpUrl("127.0.0.1:0", protocol="http")
-        )
-        settings.working_directory = (
-            aca.data_dir
-            if aca.data_dir != BeekeeperArgumentsDefaults.DEFAULT_DATA_DIR
-            else self.__exec.working_directory
-        )
-        try:
-            self._run_application(settings=settings, additional_cli_arguments=aca)
-        except CalledProcessError as e:
-            raise BeekeeperFailedToStartDuringProcessSpawnError from e
-        try:
-            self.__wait_till_ready()
-        except (AssertionError, TimeoutError) as e:
-            self._close()
-            raise BeekeeperFailedToStartNotReadyOnTimeError from e
-
-    def _run_application(self, settings: Settings, additional_cli_arguments: BeekeeperArguments) -> None:
-        assert settings.notification_endpoint is not None
-        self.__exec.run(
-            blocking=False,
-            arguments=additional_cli_arguments.copy(
-                update={
-                    "notifications_endpoint": settings.notification_endpoint,
-                    "webserver_http_endpoint": settings.ensured_http_endpoint,
-                    "data_dir": settings.ensured_working_directory,
-                }
-            ),
-            propagate_sigint=settings.propagate_sigint,
-        )
+    def run(self, additional_cli_arguments: BeekeeperArguments | None = None) -> None:
+        with self._exec.restore_arguments(additional_cli_arguments):
+            try:
+                self._run()
+            except ExecutableError as e:
+                raise BeekeeperFailedToStartError from e
 
-    def detach(self) -> int:
-        pid = self.__exec.detach()
-        self.__close_notification_server()
-        return pid
+    def _write_ports(self, editable_settings: Settings, ports: PortMatchingResult) -> None:
+        editable_settings.http_endpoint = ports.http
+        self.config.webserver_http_endpoint = ports.http
+        self.config.webserver_ws_endpoint = ports.websocket
 
     def _close(self) -> None:
         self._close_application()
-        self.__close_notification_server()
 
     def _close_application(self) -> None:
-        if self.__exec.is_running():
-            self.__exec.close(self._get_settings().close_timeout.total_seconds())
-
-    def _http_webserver_ready(self, notification: Notification[WebserverListening]) -> None:
-        """It is converted by _get_http_endpoint_from_event."""
-
-    def _get_http_endpoint_from_event(self) -> HttpUrl:
-        assert self.__notification_event_handler is not None, "Notification event handler hasn't been set"
-        # <###> if you get exception from here, and have consistent way of reproduce please report <###>
-        # make sure you didn't forget to call beekeeper.run() method
-        addr = self.__notification_event_handler.http_endpoint_from_event
-        assert addr is not None, "Endpoint from event was not set"
-        return addr
+        if self._exec.is_running():
+            self._exec.close(self._get_settings().close_timeout.total_seconds())
 
     def export_keys_wallet(
         self, wallet_name: str, wallet_password: str, extract_to: Path | None = None
     ) -> list[KeyPair]:
-        return self.__exec.export_keys_wallet(
+        return self._exec.export_keys_wallet(
             wallet_name=wallet_name,
             wallet_password=wallet_password,
             extract_to=extract_to,
         )
 
-    @abstractmethod
-    def _get_settings(self) -> Settings: ...
-
-
-class Beekeeper(BeekeeperCommon, SyncRemoteBeekeeper):
-    def run(self, *, additional_cli_arguments: BeekeeperArguments | None = None) -> None:
-        self._clear_session()
-        with self.update_settings() as settings:
-            self._run(
-                settings=cast(Settings, settings),
-                additional_cli_arguments=additional_cli_arguments,
-            )
-        self.http_endpoint = self._get_http_endpoint_from_event()
 
+class Beekeeper(RunnableBeekeeper, BeekeeperTemplate[RunnableSettingsT]):
     def _get_settings(self) -> Settings:
-        assert isinstance(self.settings, Settings)
         return self.settings
 
-    @property
-    def settings(self) -> Settings:
-        return cast(Settings, super().settings)
-
-    def _enter(self) -> Beekeeper:
+    def _enter(self) -> Beekeeper[RunnableSettingsT]:
         self.run()
         return self
 
     def teardown(self) -> None:
         self._close()
+        self._clear_session()
         super().teardown()
 
-
-class AsyncBeekeeper(BeekeeperCommon, AsyncRemoteBeekeeper):
-    def run(self, *, additional_cli_arguments: BeekeeperArguments | None = None) -> None:
-        self._clear_session()
+    def _setup_ports(self, ports: PortMatchingResult) -> None:
         with self.update_settings() as settings:
-            self._run(
-                settings=cast(Settings, settings),
-                additional_cli_arguments=additional_cli_arguments,
-            )
-        self.http_endpoint = self._get_http_endpoint_from_event()
+            self._write_ports(settings, ports)
+
 
+class AsyncBeekeeper(RunnableBeekeeper, AsyncBeekeeperTemplate[RunnableSettingsT]):
     def _get_settings(self) -> Settings:
-        assert isinstance(self.settings, Settings)
         return self.settings
 
-    @property
-    def settings(self) -> Settings:
-        return cast(Settings, super().settings)
-
-    async def _aenter(self) -> AsyncBeekeeper:
+    async def _aenter(self) -> AsyncBeekeeper[RunnableSettingsT]:
         self.run()
         return self
 
     def teardown(self) -> None:
-        self._close()
+        self.close()
+        self._clear_session()
         super().teardown()
+
+    def _setup_ports(self, ports: PortMatchingResult) -> None:
+        with self.update_settings() as settings:
+            self._write_ports(settings, ports)
diff --git a/beekeepy/beekeepy/_runnable_handle/beekeeper_callbacks.py b/beekeepy/beekeepy/_runnable_handle/beekeeper_callbacks.py
deleted file mode 100644
index 8ca36a4c..00000000
--- a/beekeepy/beekeepy/_runnable_handle/beekeeper_callbacks.py
+++ /dev/null
@@ -1,42 +0,0 @@
-from __future__ import annotations
-
-import warnings
-from abc import ABC
-from typing import TYPE_CHECKING, Any, Protocol
-
-if TYPE_CHECKING:
-    from schemas.notifications import (
-        AttemptClosingWallets,
-        Error,
-        Notification,
-        OpeningBeekeeperFailed,
-        Status,
-        WebserverListening,
-    )
-
-
-class NotificationCallback(Protocol):
-    def __call__(self, notification: Notification[Any]) -> None: ...
-
-
-class BeekeeperNotificationCallbacks(ABC):  # noqa: B024
-    def __init__(self, *args: Any, **kwargs: Any) -> None:
-        super().__init__(*args, **kwargs)
-
-    def _http_webserver_ready(self, notification: Notification[WebserverListening]) -> None:
-        self.__empty_handle_message(notification.value)
-
-    def _handle_error(self, error: Error) -> None:
-        self.__empty_handle_message(error)
-
-    def _handle_status_change(self, status: Status) -> None:
-        self.__empty_handle_message(status)
-
-    def _handle_wallets_closed(self, note: AttemptClosingWallets) -> None:
-        self.__empty_handle_message(note)
-
-    def _handle_opening_beekeeper_failed(self, info: OpeningBeekeeperFailed) -> None:
-        self.__empty_handle_message(info)
-
-    def __empty_handle_message(self, obj: Any) -> None:
-        warnings.warn(f"Notification `{type(obj).__name__}` hasn't been handled", category=RuntimeWarning, stacklevel=1)
diff --git a/beekeepy/beekeepy/_runnable_handle/beekeeper_notification_handler.py b/beekeepy/beekeepy/_runnable_handle/beekeeper_notification_handler.py
deleted file mode 100644
index 74f91779..00000000
--- a/beekeepy/beekeepy/_runnable_handle/beekeeper_notification_handler.py
+++ /dev/null
@@ -1,70 +0,0 @@
-from __future__ import annotations
-
-from threading import Event
-from typing import TYPE_CHECKING, Any
-
-from loguru import logger
-
-from beekeepy._interface.url import HttpUrl
-from beekeepy._runnable_handle.notification_handler_base import BeekeeperNotificationHandler
-
-if TYPE_CHECKING:
-    from beekeepy._runnable_handle.beekeeper_callbacks import BeekeeperNotificationCallbacks
-    from schemas.notifications import (
-        AttemptClosingWallets,
-        Error,
-        KnownNotificationT,
-        Notification,
-        OpeningBeekeeperFailed,
-        Status,
-        WebserverListening,
-    )
-
-
-class NotificationHandler(BeekeeperNotificationHandler):
-    def __init__(self, owner: BeekeeperNotificationCallbacks, *args: Any, **kwargs: Any) -> None:
-        super().__init__(*args, **kwargs)
-        self.__owner = owner
-
-        self.http_listening_event = Event()
-        self.http_endpoint_from_event: HttpUrl | None = None
-
-        self.already_working_beekeeper_event = Event()
-        self.already_working_beekeeper_http_address: HttpUrl | None = None
-        self.already_working_beekeeper_pid: int | None = None
-
-    async def on_attempt_of_closing_wallets(self, notification: Notification[AttemptClosingWallets]) -> None:
-        self.__owner._handle_wallets_closed(notification.value)
-
-    async def on_opening_beekeeper_failed(self, notification: Notification[OpeningBeekeeperFailed]) -> None:
-        self.already_working_beekeeper_http_address = HttpUrl(
-            self.__combine_url_string(
-                notification.value.connection.address,
-                notification.value.connection.port,
-            ),
-            protocol="http",
-        )
-        self.already_working_beekeeper_pid = int(notification.value.pid)
-        self.already_working_beekeeper_event.set()
-        self.__owner._handle_opening_beekeeper_failed(notification.value)
-
-    async def on_error(self, notification: Notification[Error]) -> None:
-        self.__owner._handle_error(notification.value)
-
-    async def on_status_changed(self, notification: Notification[Status]) -> None:
-        self.__owner._handle_status_change(notification.value)
-
-    async def on_http_webserver_bind(self, notification: Notification[WebserverListening]) -> None:
-        self.http_endpoint_from_event = HttpUrl(
-            self.__combine_url_string(notification.value.address, notification.value.port),
-            protocol="http",
-        )
-        self.http_listening_event.set()
-        self.__owner._http_webserver_ready(notification)
-
-    async def handle_notification(self, notification: Notification[KnownNotificationT]) -> None:
-        logger.debug(f"got notification: {notification.json()}")
-        return await super().handle_notification(notification)
-
-    def __combine_url_string(self, address: str, port: int) -> str:
-        return f"{address}:{port}"
diff --git a/beekeepy/beekeepy/_runnable_handle/match_ports.py b/beekeepy/beekeepy/_runnable_handle/match_ports.py
new file mode 100644
index 00000000..09f46fb6
--- /dev/null
+++ b/beekeepy/beekeepy/_runnable_handle/match_ports.py
@@ -0,0 +1,72 @@
+from __future__ import annotations
+
+import socket
+import ssl
+from dataclasses import dataclass, field
+from typing import Final
+
+from beekeepy._communication import HttpUrl, P2PUrl, WsUrl
+
+__all__ = ["PortMatchingResult", "match_ports"]
+
+# https://http.cat/status/426
+WEBSERVER_SPECIFIC_RESPONSE: Final[bytes] = b"426 Upgrade Required"
+
+
+@dataclass
+class PortMatchingResult:
+    http: HttpUrl | None = None
+    https: HttpUrl | None = None
+    websocket: WsUrl | None = None
+    p2p: list[P2PUrl] = field(default_factory=list)
+
+
+def test_http(address: HttpUrl) -> bool:
+    assert address.port is not None, "HTTP CHECK: Port has to be set"
+    try:
+        with socket.create_connection((address.address, address.port), timeout=1) as sock:
+            sock.sendall(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
+            response = sock.recv(1024)
+            return response.startswith(b"HTTP") and WEBSERVER_SPECIFIC_RESPONSE not in response
+    except (OSError, socket.timeout, ConnectionRefusedError):
+        return False
+
+
+def test_https(address: HttpUrl) -> bool:
+    assert address.port is not None, "HTTPS CHECK: Port has to be set"
+    try:
+        context = ssl.create_default_context()
+        with socket.create_connection((address.address, address.port), timeout=1) as sock, context.wrap_socket(
+            sock, server_hostname="localhost"
+        ) as ssock:
+            ssock.sendall(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
+            response = ssock.recv(1024)
+            return response.startswith(b"HTTP") and WEBSERVER_SPECIFIC_RESPONSE not in response
+    except (OSError, socket.timeout, ConnectionRefusedError, ssl.SSLError):
+        return False
+
+
+def test_websocket(address: WsUrl) -> bool:
+    assert address.port is not None, "WS CHECK: Port has to be set"
+    try:
+        with socket.create_connection((address.address, address.port), timeout=1) as sock:
+            sock.sendall(b"GET / HTTP/1.1\r\nHost: localhost\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n\r\n")
+            response = sock.recv(1024)
+            return WEBSERVER_SPECIFIC_RESPONSE in response
+    except (OSError, socket.timeout, ConnectionRefusedError):
+        return False
+
+
+def match_ports(ports: list[int], *, address: str = "127.0.0.1") -> PortMatchingResult:
+    categories = PortMatchingResult()
+    for port in ports:
+        if categories.http is None and test_http(http_result := HttpUrl.factory(port=port, address=address)):
+            categories.http = http_result
+        elif categories.https is None and test_https(http_result := HttpUrl.factory(port=port, address=address)):
+            categories.https = http_result
+        elif categories.websocket is None and test_websocket(ws_result := WsUrl.factory(port=port, address=address)):
+            categories.websocket = ws_result
+        else:
+            categories.p2p.append(P2PUrl.factory(port=port, address=address))
+
+    return categories
diff --git a/beekeepy/beekeepy/_runnable_handle/notification_handler_base.py b/beekeepy/beekeepy/_runnable_handle/notification_handler_base.py
deleted file mode 100644
index 1e3195be..00000000
--- a/beekeepy/beekeepy/_runnable_handle/notification_handler_base.py
+++ /dev/null
@@ -1,29 +0,0 @@
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-from beekeepy._communication.appbase_notification_handler import AppbaseNotificationHandler
-from beekeepy._communication.notification_decorator import notification
-from schemas.notifications import AttemptClosingWallets, OpeningBeekeeperFailed
-
-if TYPE_CHECKING:
-    from schemas.notifications import Notification
-
-
-class BeekeeperNotificationHandler(AppbaseNotificationHandler):
-    @notification(AttemptClosingWallets)
-    async def _on_attempt_of_closing_wallets(self, notification: Notification[AttemptClosingWallets]) -> None:
-        await self.on_attempt_of_closing_wallets(notification)
-
-    @notification(OpeningBeekeeperFailed)
-    async def _on_opening_beekeeper_failed(self, notification: Notification[OpeningBeekeeperFailed]) -> None:
-        await self.on_opening_beekeeper_failed(notification)
-
-    async def on_attempt_of_closing_wallets(self, notification: Notification[AttemptClosingWallets]) -> None:
-        """Called when beekeeper attempts to close wallets in session with given token."""
-
-    async def on_opening_beekeeper_failed(self, notification: Notification[OpeningBeekeeperFailed]) -> None:
-        """Called, when beekeeper failed to start.
-
-        That is because of already running other beekeeper in selected working directory.
-        """
diff --git a/beekeepy/beekeepy/_runnable_handle/runnable_handle.py b/beekeepy/beekeepy/_runnable_handle/runnable_handle.py
new file mode 100644
index 00000000..5efd171f
--- /dev/null
+++ b/beekeepy/beekeepy/_runnable_handle/runnable_handle.py
@@ -0,0 +1,256 @@
+from __future__ import annotations
+
+import time
+import warnings
+from abc import ABC, abstractmethod
+from datetime import timedelta
+from pathlib import Path
+from subprocess import SubprocessError
+from typing import TYPE_CHECKING, Any, Generic, TypeVar, cast
+
+from loguru import logger as default_logger
+
+from beekeepy._communication import HttpUrl, P2PUrl, WsUrl
+from beekeepy._executable import ArgumentT, ConfigT, Executable
+from beekeepy._remote_handle import AppStatusProbe
+from beekeepy._runnable_handle.match_ports import PortMatchingResult, match_ports
+from beekeepy._runnable_handle.settings import Settings
+from beekeepy.exceptions import (
+    ApiNotFoundError,
+    FailedToDetectReservedPortsError,
+    FailedToStartExecutableError,
+)
+
+if TYPE_CHECKING:
+    from loguru import Logger
+
+    from schemas.apis.app_status_api import GetAppStatus
+
+
+ExecutableT = TypeVar("ExecutableT", bound=Executable[Any, Any])
+SettingsT = TypeVar("SettingsT", bound=Settings)
+T = TypeVar("T")
+
+
+class RunnableHandle(ABC, Generic[ExecutableT, ConfigT, ArgumentT, SettingsT]):
+    def __init__(self, *args: Any, logger: Logger | None = None, **kwargs: Any) -> None:
+        super().__init__(*args, **kwargs)
+        self._logger = logger or default_logger
+        self._exec = self._construct_executable()
+
+    @property
+    def pid(self) -> int:
+        """Returns pid of started executable. Note: Proxy method to Executable.pid."""
+        return self._exec.pid
+
+    @property
+    def arguments(self) -> ArgumentT:
+        """Returns arguments for given binary. Note: Proxy method to Executable.arguments."""
+        return cast(ArgumentT, self._exec.arguments)
+
+    @property
+    def config(self) -> ConfigT:
+        """Returns config for given binary. Note: Proxy method to Executable.config."""
+        return cast(ConfigT, self._exec.config)
+
+    def is_running(self) -> bool:
+        """Returns is process running. Note: Proxy method to Executable.is_running."""
+        return self._exec.is_running()
+
+    def detach(self) -> int:
+        """Detaches process and allows to keep it after closing python script."""
+        return self._exec.detach()
+
+    def close(self) -> None:
+        """Closes running process. If process is not running, method does nothing."""
+        if self.is_running():
+            self._exec.close(timeout_secs=self._get_settings().close_timeout.total_seconds())
+
+    def get_help_text(self) -> str:
+        """Returns help printed by executable."""
+        self.__show_warning_if_executable_already_running()
+        return self._exec.get_help_text()
+
+    def get_version(self) -> str:
+        """Returns version string printed by executable."""
+        self.__show_warning_if_executable_already_running()
+        return self._exec.version()
+
+    def generate_default_config_from_executable(self) -> ConfigT:
+        """Returns config generated by executable."""
+        self.__show_warning_if_executable_already_running()
+        return cast(ConfigT, self._exec.generate_default_config())
+
+    def _run(
+        self,
+        *,
+        environment_variables: dict[str, str] | None = None,
+        perform_unification: bool = True,
+        blocking: bool = False,
+        save_config: bool = True,
+    ) -> None:
+        """
+        Runs executable and unifies arguments.
+
+        Note: This method should be called by RunnableHandleChild.run, which is not defined by this interface!
+
+        Keyword Arguments:
+            environment_variables -- additional environment variables to set before launching executable
+            additional_cli_arguments -- arguments to add to executable invocation
+            perform_unification -- if set to true, chosen values will be written to config and cli arguments
+        """
+        settings = self._get_settings().copy()
+
+        settings.working_directory = self.__choose_working_directory(settings=settings)
+        settings.http_endpoint = self.__choose_http_endpoint(settings=settings)
+
+        if perform_unification:
+            self._unify_cli_arguments(settings.working_directory, settings.http_endpoint)
+            self._unify_config(settings.working_directory, settings.http_endpoint)
+
+        try:
+            self._exec._run(
+                blocking=blocking,
+                environ=environment_variables,
+                propagate_sigint=settings.propagate_sigint,
+                save_config=save_config,
+            )
+            if blocking:
+                return
+        except SubprocessError as e:
+            raise FailedToStartExecutableError from e
+        try:
+            self._wait_for_app_to_start()
+        except TimeoutError as e:
+            raise FailedToDetectReservedPortsError from e
+        self._setup_ports(self.__discover_ports())
+
+    @abstractmethod
+    def _construct_executable(self) -> ExecutableT:
+        """Returns executable instance."""
+
+    @abstractmethod
+    def _get_settings(self) -> SettingsT:
+        """Returns settings hold by child class. Used only for read-only purposes."""
+
+    def _get_working_directory_from_cli_arguments(self) -> Path | None:
+        """Returns working directory from specified cli arguments in executable (if specified)."""
+        return None
+
+    def _get_http_endpoint_from_cli_arguments(self) -> HttpUrl | None:
+        """Returns http endpoint from specified cli arguments in executable (if specified)."""
+        return None
+
+    def _get_working_directory_from_config(self) -> Path | None:
+        """Returns working directory from specified config in executable (if specified)."""
+        return None
+
+    def _get_http_endpoint_from_config(self) -> HttpUrl | None:
+        """Returns http endpoint from specified config in executable (if specified)."""
+        return None
+
+    @abstractmethod
+    def _unify_cli_arguments(self, working_directory: Path, http_endpoint: HttpUrl) -> None:
+        """
+        Writes selected values to given cli arguments.
+
+        Args:
+            working_directory -- chosen working path to be set in cli arguments.
+            http_endpoint -- chosen http endpoint to be set in cli arguments.
+        """
+
+    @abstractmethod
+    def _unify_config(self, working_directory: Path, http_endpoint: HttpUrl) -> None:
+        """
+        Writes selected values to config in executable.
+
+        Args:
+            working_directory -- chosen working path to be set in config.
+            http_endpoint -- chosen http endpoint to be set in config.
+        """
+
+    def _setup_ports(self, ports: PortMatchingResult) -> None:
+        """
+        Setup ports after startup.
+
+        Args:
+            ports -- list of ports reserved by started application.
+        """
+
+    def _wait_for_app_to_start(self) -> None:
+        """Waits for application to start."""
+        while not self._exec.reserved_ports():
+            if not self._exec.is_running():
+                raise FailedToStartExecutableError
+            time.sleep(0.1)
+
+    def __choose_working_directory(self, settings: Settings) -> Path:
+        return self.__choose_value(
+            default_value=Path.cwd(),
+            argument_value=self._get_working_directory_from_cli_arguments(),
+            config_value=self._get_working_directory_from_config(),
+            settings_value=settings.working_directory,
+        )
+
+    def __choose_http_endpoint(self, settings: Settings) -> HttpUrl:
+        return self.__choose_value(
+            default_value=HttpUrl("http://0.0.0.0:0"),
+            argument_value=self._get_http_endpoint_from_cli_arguments(),
+            config_value=self._get_http_endpoint_from_config(),
+            settings_value=settings.http_endpoint,
+        )
+
+    def __show_warning_if_executable_already_running(self) -> None:
+        if self.is_running():
+            warnings.warn("Invoking executable that is already running!", stacklevel=2)
+
+    @classmethod
+    def __choose_value(
+        cls,
+        default_value: T,
+        argument_value: T | None = None,
+        config_value: T | None = None,
+        settings_value: T | None = None,
+    ) -> T:
+        if argument_value is not None:
+            return argument_value
+        if config_value is not None:
+            return config_value
+        if settings_value is not None:
+            return settings_value
+        return default_value
+
+    def __discover_ports(self) -> PortMatchingResult:
+        reserved_ports = self._exec.reserved_ports()
+        matched_ports = match_ports(reserved_ports)
+        if matched_ports.http is None:
+            warnings.warn("Given executable probably does not provide http network access", stacklevel=3)
+            return matched_ports
+
+        handle = AppStatusProbe(settings=Settings(http_endpoint=matched_ports.http, timeout=timedelta(seconds=1)))
+        status: None | GetAppStatus = None
+        try:
+            status = handle.api.get_app_status()
+        except ApiNotFoundError:
+            warnings.warn(
+                "HTTP port detected, but cannot obtain further information. app_status_api plugin is not enabled!",
+                stacklevel=3,
+            )
+            return matched_ports
+
+        assert status is not None, "Error has not been caught and further port discovery started"
+        http = status.webservers.HTTP
+        assert http, "Http cannot be None, as AppStatusProbe is already connected via http"
+        assert (
+            http.port == matched_ports.http.port
+        ), "Http cannot differ from detected ports, because it is already connected"
+
+        ws = status.webservers.WS
+        if ws and matched_ports.websocket and ws.port != matched_ports.websocket.port:
+            matched_ports.websocket = WsUrl.factory(port=ws.port)
+
+        p2p = status.webservers.P2P
+        if p2p and p2p.port not in [x.port for x in matched_ports.p2p]:
+            matched_ports.p2p = [P2PUrl.factory(port=p2p.port)]
+
+        return matched_ports
diff --git a/beekeepy/beekeepy/_runnable_handle/settings.py b/beekeepy/beekeepy/_runnable_handle/settings.py
index c989f3e4..7adf312c 100644
--- a/beekeepy/beekeepy/_runnable_handle/settings.py
+++ b/beekeepy/beekeepy/_runnable_handle/settings.py
@@ -11,7 +11,7 @@ from beekeepy._remote_handle.settings import Settings as RemoteHandleSettings
 
 
 class Settings(RemoteHandleSettings):
-    """Defines parameters for beekeeper how to start and behave."""
+    """Defines parameters for runnable handles how to start and behave."""
 
     class EnvironNames(RemoteHandleSettings.EnvironNames):
         WORKING_DIRECTORY: ClassVar[str] = "BEEKEEPY_WORKING_DIRECTORY"
@@ -34,16 +34,6 @@ class Settings(RemoteHandleSettings):
     In case of local beekeeper, this address will be used for beekeeper to start listening on.
     """
 
-    communicator: type[AbstractCommunicator] | AbstractCommunicator | None = None
-    """
-    Defines class to be used for network handling. Can be given as class or instance.
-
-    Note: If set to none, handles will use preferred communicators
-    """
-
-    notification_endpoint: HttpUrl | None = None
-    """Endpoint to use for reverse communication between beekeeper and python."""
-
     binary_path: Path | None = None
     """Alternative path to beekeeper binary."""
 
@@ -66,6 +56,7 @@ class Settings(RemoteHandleSettings):
         EnvironNames.INITIALIZATION_TIMEOUT,
         lambda x: (Settings.Defaults.INITIALIZATION_TIMEOUT if x is None else timedelta(seconds=int(x))),
     )
+    """Affects time handle waits for beekeeper to start."""
 
     @property
     def ensured_working_directory(self) -> Path:
diff --git a/beekeepy/beekeepy/exceptions/__init__.py b/beekeepy/beekeepy/exceptions/__init__.py
index 6faeb756..03221948 100644
--- a/beekeepy/beekeepy/exceptions/__init__.py
+++ b/beekeepy/beekeepy/exceptions/__init__.py
@@ -9,6 +9,7 @@ from beekeepy.exceptions.base import (
     CommunicationError,
     CommunicationResponseT,
     DetectableError,
+    ExecutableError,
     InvalidatedStateError,
     Json,
     OverseerError,
@@ -27,7 +28,6 @@ from beekeepy.exceptions.common import (
     NotPositiveTimeError,
     ResponseNotReadyError,
     TimeoutExceededError,
-    TimeoutReachWhileCloseError,
     TimeTooBigError,
     UnknownDecisionPathError,
     WalletIsLockedError,
@@ -46,6 +46,12 @@ from beekeepy.exceptions.detectable import (
     NoWalletWithSuchNameError,
     WalletWithSuchNameAlreadyExistsError,
 )
+from beekeepy.exceptions.executable import (
+    ExecutableIsNotRunningError,
+    FailedToDetectReservedPortsError,
+    FailedToStartExecutableError,
+    TimeoutReachWhileCloseError,
+)
 from beekeepy.exceptions.overseer import (
     ApiNotFoundError,
     DifferenceBetweenAmountOfRequestsAndResponsesError,
@@ -79,6 +85,10 @@ __all__ = [
     "DetectableError",
     "DifferenceBetweenAmountOfRequestsAndResponsesError",
     "ErrorInResponseError",
+    "ExecutableError",
+    "ExecutableIsNotRunningError",
+    "FailedToDetectReservedPortsError",
+    "FailedToStartExecutableError",
     "GroupedErrorsError",
     "InvalidAccountNameError",
     "InvalidatedStateByClosingBeekeeperError",
diff --git a/beekeepy/beekeepy/exceptions/base.py b/beekeepy/beekeepy/exceptions/base.py
index 61c77cc9..d59b0d84 100644
--- a/beekeepy/beekeepy/exceptions/base.py
+++ b/beekeepy/beekeepy/exceptions/base.py
@@ -189,3 +189,7 @@ class OverseerError(CommunicationError, ABC):
     @abstractmethod
     def retry(self) -> bool:
         """Used by overseer to determine if retry should be performed if such error occurs."""
+
+
+class ExecutableError(BeekeepyError, ABC):
+    """Base class for errors related to handling executable."""
diff --git a/beekeepy/beekeepy/exceptions/executable.py b/beekeepy/beekeepy/exceptions/executable.py
new file mode 100644
index 00000000..fafda8de
--- /dev/null
+++ b/beekeepy/beekeepy/exceptions/executable.py
@@ -0,0 +1,23 @@
+from __future__ import annotations
+
+from beekeepy.exceptions.base import ExecutableError
+
+
+class TimeoutReachWhileCloseError(ExecutableError):
+    """Raises when executable did not closed during specified timeout."""
+
+    def __init__(self) -> None:
+        """Constructor."""
+        super().__init__("Process was force-closed with SIGKILL, because didn't close before timeout")
+
+
+class ExecutableIsNotRunningError(ExecutableError):
+    """Raises when executable is not running, but user requests action on running instance."""
+
+
+class FailedToStartExecutableError(ExecutableError):
+    """Raises when executable failed to start."""
+
+
+class FailedToDetectReservedPortsError(ExecutableError):
+    """Raises when port lookup procedure fails."""
diff --git a/beekeepy/poetry.lock b/beekeepy/poetry.lock
index feafa092..21c71f37 100644
--- a/beekeepy/poetry.lock
+++ b/beekeepy/poetry.lock
@@ -13,92 +13,92 @@ files = [
 
 [[package]]
 name = "aiohttp"
-version = "3.11.14"
+version = "3.11.16"
 description = "Async http client/server framework (asyncio)"
 optional = false
 python-versions = ">=3.9"
 files = [
-    {file = "aiohttp-3.11.14-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e2bc827c01f75803de77b134afdbf74fa74b62970eafdf190f3244931d7a5c0d"},
-    {file = "aiohttp-3.11.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e365034c5cf6cf74f57420b57682ea79e19eb29033399dd3f40de4d0171998fa"},
-    {file = "aiohttp-3.11.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c32593ead1a8c6aabd58f9d7ee706e48beac796bb0cb71d6b60f2c1056f0a65f"},
-    {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4e7c7ec4146a94a307ca4f112802a8e26d969018fabed526efc340d21d3e7d0"},
-    {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8b2df9feac55043759aa89f722a967d977d80f8b5865a4153fc41c93b957efc"},
-    {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c7571f99525c76a6280f5fe8e194eeb8cb4da55586c3c61c59c33a33f10cfce7"},
-    {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b59d096b5537ec7c85954cb97d821aae35cfccce3357a2cafe85660cc6295628"},
-    {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b42dbd097abb44b3f1156b4bf978ec5853840802d6eee2784857be11ee82c6a0"},
-    {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b05774864c87210c531b48dfeb2f7659407c2dda8643104fb4ae5e2c311d12d9"},
-    {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4e2e8ef37d4bc110917d038807ee3af82700a93ab2ba5687afae5271b8bc50ff"},
-    {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e9faafa74dbb906b2b6f3eb9942352e9e9db8d583ffed4be618a89bd71a4e914"},
-    {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7e7abe865504f41b10777ac162c727af14e9f4db9262e3ed8254179053f63e6d"},
-    {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:4848ae31ad44330b30f16c71e4f586cd5402a846b11264c412de99fa768f00f3"},
-    {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2d0b46abee5b5737cb479cc9139b29f010a37b1875ee56d142aefc10686a390b"},
-    {file = "aiohttp-3.11.14-cp310-cp310-win32.whl", hash = "sha256:a0d2c04a623ab83963576548ce098baf711a18e2c32c542b62322a0b4584b990"},
-    {file = "aiohttp-3.11.14-cp310-cp310-win_amd64.whl", hash = "sha256:5409a59d5057f2386bb8b8f8bbcfb6e15505cedd8b2445db510563b5d7ea1186"},
-    {file = "aiohttp-3.11.14-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f296d637a50bb15fb6a229fbb0eb053080e703b53dbfe55b1e4bb1c5ed25d325"},
-    {file = "aiohttp-3.11.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec6cd1954ca2bbf0970f531a628da1b1338f594bf5da7e361e19ba163ecc4f3b"},
-    {file = "aiohttp-3.11.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:572def4aad0a4775af66d5a2b5923c7de0820ecaeeb7987dcbccda2a735a993f"},
-    {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c68e41c4d576cd6aa6c6d2eddfb32b2acfb07ebfbb4f9da991da26633a3db1a"},
-    {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b8bbfc8111826aa8363442c0fc1f5751456b008737ff053570f06a151650b3"},
-    {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b0a200e85da5c966277a402736a96457b882360aa15416bf104ca81e6f5807b"},
-    {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d173c0ac508a2175f7c9a115a50db5fd3e35190d96fdd1a17f9cb10a6ab09aa1"},
-    {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:413fe39fd929329f697f41ad67936f379cba06fcd4c462b62e5b0f8061ee4a77"},
-    {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65c75b14ee74e8eeff2886321e76188cbe938d18c85cff349d948430179ad02c"},
-    {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:321238a42ed463848f06e291c4bbfb3d15ba5a79221a82c502da3e23d7525d06"},
-    {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:59a05cdc636431f7ce843c7c2f04772437dd816a5289f16440b19441be6511f1"},
-    {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:daf20d9c3b12ae0fdf15ed92235e190f8284945563c4b8ad95b2d7a31f331cd3"},
-    {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:05582cb2d156ac7506e68b5eac83179faedad74522ed88f88e5861b78740dc0e"},
-    {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:12c5869e7ddf6b4b1f2109702b3cd7515667b437da90a5a4a50ba1354fe41881"},
-    {file = "aiohttp-3.11.14-cp311-cp311-win32.whl", hash = "sha256:92868f6512714efd4a6d6cb2bfc4903b997b36b97baea85f744229f18d12755e"},
-    {file = "aiohttp-3.11.14-cp311-cp311-win_amd64.whl", hash = "sha256:bccd2cb7aa5a3bfada72681bdb91637094d81639e116eac368f8b3874620a654"},
-    {file = "aiohttp-3.11.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:70ab0f61c1a73d3e0342cedd9a7321425c27a7067bebeeacd509f96695b875fc"},
-    {file = "aiohttp-3.11.14-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:602d4db80daf4497de93cb1ce00b8fc79969c0a7cf5b67bec96fa939268d806a"},
-    {file = "aiohttp-3.11.14-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a8a0d127c10b8d89e69bbd3430da0f73946d839e65fec00ae48ca7916a31948"},
-    {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9f835cdfedcb3f5947304e85b8ca3ace31eef6346d8027a97f4de5fb687534"},
-    {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8aa5c68e1e68fff7cd3142288101deb4316b51f03d50c92de6ea5ce646e6c71f"},
-    {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b512f1de1c688f88dbe1b8bb1283f7fbeb7a2b2b26e743bb2193cbadfa6f307"},
-    {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc9253069158d57e27d47a8453d8a2c5a370dc461374111b5184cf2f147a3cc3"},
-    {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b2501f1b981e70932b4a552fc9b3c942991c7ae429ea117e8fba57718cdeed0"},
-    {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:28a3d083819741592685762d51d789e6155411277050d08066537c5edc4066e6"},
-    {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0df3788187559c262922846087e36228b75987f3ae31dd0a1e5ee1034090d42f"},
-    {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e73fa341d8b308bb799cf0ab6f55fc0461d27a9fa3e4582755a3d81a6af8c09"},
-    {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:51ba80d473eb780a329d73ac8afa44aa71dfb521693ccea1dea8b9b5c4df45ce"},
-    {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8d1dd75aa4d855c7debaf1ef830ff2dfcc33f893c7db0af2423ee761ebffd22b"},
-    {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41cf0cefd9e7b5c646c2ef529c8335e7eafd326f444cc1cdb0c47b6bc836f9be"},
-    {file = "aiohttp-3.11.14-cp312-cp312-win32.whl", hash = "sha256:948abc8952aff63de7b2c83bfe3f211c727da3a33c3a5866a0e2cf1ee1aa950f"},
-    {file = "aiohttp-3.11.14-cp312-cp312-win_amd64.whl", hash = "sha256:3b420d076a46f41ea48e5fcccb996f517af0d406267e31e6716f480a3d50d65c"},
-    {file = "aiohttp-3.11.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d14e274828561db91e4178f0057a915f3af1757b94c2ca283cb34cbb6e00b50"},
-    {file = "aiohttp-3.11.14-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f30fc72daf85486cdcdfc3f5e0aea9255493ef499e31582b34abadbfaafb0965"},
-    {file = "aiohttp-3.11.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4edcbe34e6dba0136e4cabf7568f5a434d89cc9de5d5155371acda275353d228"},
-    {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a7169ded15505f55a87f8f0812c94c9412623c744227b9e51083a72a48b68a5"},
-    {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad1f2fb9fe9b585ea4b436d6e998e71b50d2b087b694ab277b30e060c434e5db"},
-    {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20412c7cc3720e47a47e63c0005f78c0c2370020f9f4770d7fc0075f397a9fb0"},
-    {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dd9766da617855f7e85f27d2bf9a565ace04ba7c387323cd3e651ac4329db91"},
-    {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:599b66582f7276ebefbaa38adf37585e636b6a7a73382eb412f7bc0fc55fb73d"},
-    {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b41693b7388324b80f9acfabd479bd1c84f0bc7e8f17bab4ecd9675e9ff9c734"},
-    {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:86135c32d06927339c8c5e64f96e4eee8825d928374b9b71a3c42379d7437058"},
-    {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:04eb541ce1e03edc1e3be1917a0f45ac703e913c21a940111df73a2c2db11d73"},
-    {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dc311634f6f28661a76cbc1c28ecf3b3a70a8edd67b69288ab7ca91058eb5a33"},
-    {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:69bb252bfdca385ccabfd55f4cd740d421dd8c8ad438ded9637d81c228d0da49"},
-    {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b86efe23684b58a88e530c4ab5b20145f102916bbb2d82942cafec7bd36a647"},
-    {file = "aiohttp-3.11.14-cp313-cp313-win32.whl", hash = "sha256:b9c60d1de973ca94af02053d9b5111c4fbf97158e139b14f1be68337be267be6"},
-    {file = "aiohttp-3.11.14-cp313-cp313-win_amd64.whl", hash = "sha256:0a29be28e60e5610d2437b5b2fed61d6f3dcde898b57fb048aa5079271e7f6f3"},
-    {file = "aiohttp-3.11.14-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:14fc03508359334edc76d35b2821832f092c8f092e4b356e74e38419dfe7b6de"},
-    {file = "aiohttp-3.11.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92007c89a8cb7be35befa2732b0b32bf3a394c1b22ef2dff0ef12537d98a7bda"},
-    {file = "aiohttp-3.11.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6d3986112e34eaa36e280dc8286b9dd4cc1a5bcf328a7f147453e188f6fe148f"},
-    {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:749f1eb10e51dbbcdba9df2ef457ec060554842eea4d23874a3e26495f9e87b1"},
-    {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:781c8bd423dcc4641298c8c5a2a125c8b1c31e11f828e8d35c1d3a722af4c15a"},
-    {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:997b57e38aa7dc6caab843c5e042ab557bc83a2f91b7bd302e3c3aebbb9042a1"},
-    {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a8b0321e40a833e381d127be993b7349d1564b756910b28b5f6588a159afef3"},
-    {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8778620396e554b758b59773ab29c03b55047841d8894c5e335f12bfc45ebd28"},
-    {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e906da0f2bcbf9b26cc2b144929e88cb3bf943dd1942b4e5af066056875c7618"},
-    {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:87f0e003fb4dd5810c7fbf47a1239eaa34cd929ef160e0a54c570883125c4831"},
-    {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7f2dadece8b85596ac3ab1ec04b00694bdd62abc31e5618f524648d18d9dd7fa"},
-    {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:fe846f0a98aa9913c2852b630cd39b4098f296e0907dd05f6c7b30d911afa4c3"},
-    {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ced66c5c6ad5bcaf9be54560398654779ec1c3695f1a9cf0ae5e3606694a000a"},
-    {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a40087b82f83bd671cbeb5f582c233d196e9653220404a798798bfc0ee189fff"},
-    {file = "aiohttp-3.11.14-cp39-cp39-win32.whl", hash = "sha256:95d7787f2bcbf7cb46823036a8d64ccfbc2ffc7d52016b4044d901abceeba3db"},
-    {file = "aiohttp-3.11.14-cp39-cp39-win_amd64.whl", hash = "sha256:22a8107896877212130c58f74e64b77f7007cb03cea8698be317272643602d45"},
-    {file = "aiohttp-3.11.14.tar.gz", hash = "sha256:d6edc538c7480fa0a3b2bdd705f8010062d74700198da55d16498e1b49549b9c"},
+    {file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb46bb0f24813e6cede6cc07b1961d4b04f331f7112a23b5e21f567da4ee50aa"},
+    {file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:54eb3aead72a5c19fad07219acd882c1643a1027fbcdefac9b502c267242f955"},
+    {file = "aiohttp-3.11.16-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:38bea84ee4fe24ebcc8edeb7b54bf20f06fd53ce4d2cc8b74344c5b9620597fd"},
+    {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0666afbe984f6933fe72cd1f1c3560d8c55880a0bdd728ad774006eb4241ecd"},
+    {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba92a2d9ace559a0a14b03d87f47e021e4fa7681dc6970ebbc7b447c7d4b7cd"},
+    {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ad1d59fd7114e6a08c4814983bb498f391c699f3c78712770077518cae63ff7"},
+    {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b88a2bf26965f2015a771381624dd4b0839034b70d406dc74fd8be4cc053e3"},
+    {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:576f5ca28d1b3276026f7df3ec841ae460e0fc3aac2a47cbf72eabcfc0f102e1"},
+    {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a2a450bcce4931b295fc0848f384834c3f9b00edfc2150baafb4488c27953de6"},
+    {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:37dcee4906454ae377be5937ab2a66a9a88377b11dd7c072df7a7c142b63c37c"},
+    {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4d0c970c0d602b1017e2067ff3b7dac41c98fef4f7472ec2ea26fd8a4e8c2149"},
+    {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:004511d3413737700835e949433536a2fe95a7d0297edd911a1e9705c5b5ea43"},
+    {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c15b2271c44da77ee9d822552201180779e5e942f3a71fb74e026bf6172ff287"},
+    {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad9509ffb2396483ceacb1eee9134724443ee45b92141105a4645857244aecc8"},
+    {file = "aiohttp-3.11.16-cp310-cp310-win32.whl", hash = "sha256:634d96869be6c4dc232fc503e03e40c42d32cfaa51712aee181e922e61d74814"},
+    {file = "aiohttp-3.11.16-cp310-cp310-win_amd64.whl", hash = "sha256:938f756c2b9374bbcc262a37eea521d8a0e6458162f2a9c26329cc87fdf06534"},
+    {file = "aiohttp-3.11.16-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8cb0688a8d81c63d716e867d59a9ccc389e97ac7037ebef904c2b89334407180"},
+    {file = "aiohttp-3.11.16-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ad1fb47da60ae1ddfb316f0ff16d1f3b8e844d1a1e154641928ea0583d486ed"},
+    {file = "aiohttp-3.11.16-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df7db76400bf46ec6a0a73192b14c8295bdb9812053f4fe53f4e789f3ea66bbb"},
+    {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc3a145479a76ad0ed646434d09216d33d08eef0d8c9a11f5ae5cdc37caa3540"},
+    {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d007aa39a52d62373bd23428ba4a2546eed0e7643d7bf2e41ddcefd54519842c"},
+    {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6ddd90d9fb4b501c97a4458f1c1720e42432c26cb76d28177c5b5ad4e332601"},
+    {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a2f451849e6b39e5c226803dcacfa9c7133e9825dcefd2f4e837a2ec5a3bb98"},
+    {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8df6612df74409080575dca38a5237282865408016e65636a76a2eb9348c2567"},
+    {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:78e6e23b954644737e385befa0deb20233e2dfddf95dd11e9db752bdd2a294d3"},
+    {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:696ef00e8a1f0cec5e30640e64eca75d8e777933d1438f4facc9c0cdf288a810"},
+    {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3538bc9fe1b902bef51372462e3d7c96fce2b566642512138a480b7adc9d508"},
+    {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3ab3367bb7f61ad18793fea2ef71f2d181c528c87948638366bf1de26e239183"},
+    {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:56a3443aca82abda0e07be2e1ecb76a050714faf2be84256dae291182ba59049"},
+    {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:61c721764e41af907c9d16b6daa05a458f066015abd35923051be8705108ed17"},
+    {file = "aiohttp-3.11.16-cp311-cp311-win32.whl", hash = "sha256:3e061b09f6fa42997cf627307f220315e313ece74907d35776ec4373ed718b86"},
+    {file = "aiohttp-3.11.16-cp311-cp311-win_amd64.whl", hash = "sha256:745f1ed5e2c687baefc3c5e7b4304e91bf3e2f32834d07baaee243e349624b24"},
+    {file = "aiohttp-3.11.16-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:911a6e91d08bb2c72938bc17f0a2d97864c531536b7832abee6429d5296e5b27"},
+    {file = "aiohttp-3.11.16-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac13b71761e49d5f9e4d05d33683bbafef753e876e8e5a7ef26e937dd766713"},
+    {file = "aiohttp-3.11.16-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fd36c119c5d6551bce374fcb5c19269638f8d09862445f85a5a48596fd59f4bb"},
+    {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d489d9778522fbd0f8d6a5c6e48e3514f11be81cb0a5954bdda06f7e1594b321"},
+    {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69a2cbd61788d26f8f1e626e188044834f37f6ae3f937bd9f08b65fc9d7e514e"},
+    {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd464ba806e27ee24a91362ba3621bfc39dbbb8b79f2e1340201615197370f7c"},
+    {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce63ae04719513dd2651202352a2beb9f67f55cb8490c40f056cea3c5c355ce"},
+    {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b00dd520d88eac9d1768439a59ab3d145065c91a8fab97f900d1b5f802895e"},
+    {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f6428fee52d2bcf96a8aa7b62095b190ee341ab0e6b1bcf50c615d7966fd45b"},
+    {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:13ceac2c5cdcc3f64b9015710221ddf81c900c5febc505dbd8f810e770011540"},
+    {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fadbb8f1d4140825069db3fedbbb843290fd5f5bc0a5dbd7eaf81d91bf1b003b"},
+    {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6a792ce34b999fbe04a7a71a90c74f10c57ae4c51f65461a411faa70e154154e"},
+    {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f4065145bf69de124accdd17ea5f4dc770da0a6a6e440c53f6e0a8c27b3e635c"},
+    {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa73e8c2656a3653ae6c307b3f4e878a21f87859a9afab228280ddccd7369d71"},
+    {file = "aiohttp-3.11.16-cp312-cp312-win32.whl", hash = "sha256:f244b8e541f414664889e2c87cac11a07b918cb4b540c36f7ada7bfa76571ea2"},
+    {file = "aiohttp-3.11.16-cp312-cp312-win_amd64.whl", hash = "sha256:23a15727fbfccab973343b6d1b7181bfb0b4aa7ae280f36fd2f90f5476805682"},
+    {file = "aiohttp-3.11.16-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a3814760a1a700f3cfd2f977249f1032301d0a12c92aba74605cfa6ce9f78489"},
+    {file = "aiohttp-3.11.16-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9b751a6306f330801665ae69270a8a3993654a85569b3469662efaad6cf5cc50"},
+    {file = "aiohttp-3.11.16-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ad497f38a0d6c329cb621774788583ee12321863cd4bd9feee1effd60f2ad133"},
+    {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca37057625693d097543bd88076ceebeb248291df9d6ca8481349efc0b05dcd0"},
+    {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5abcbba9f4b463a45c8ca8b7720891200658f6f46894f79517e6cd11f3405ca"},
+    {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f420bfe862fb357a6d76f2065447ef6f484bc489292ac91e29bc65d2d7a2c84d"},
+    {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58ede86453a6cf2d6ce40ef0ca15481677a66950e73b0a788917916f7e35a0bb"},
+    {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fdec0213244c39973674ca2a7f5435bf74369e7d4e104d6c7473c81c9bcc8c4"},
+    {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72b1b03fb4655c1960403c131740755ec19c5898c82abd3961c364c2afd59fe7"},
+    {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:780df0d837276276226a1ff803f8d0fa5f8996c479aeef52eb040179f3156cbd"},
+    {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ecdb8173e6c7aa09eee342ac62e193e6904923bd232e76b4157ac0bfa670609f"},
+    {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a6db7458ab89c7d80bc1f4e930cc9df6edee2200127cfa6f6e080cf619eddfbd"},
+    {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2540ddc83cc724b13d1838026f6a5ad178510953302a49e6d647f6e1de82bc34"},
+    {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3b4e6db8dc4879015b9955778cfb9881897339c8fab7b3676f8433f849425913"},
+    {file = "aiohttp-3.11.16-cp313-cp313-win32.whl", hash = "sha256:493910ceb2764f792db4dc6e8e4b375dae1b08f72e18e8f10f18b34ca17d0979"},
+    {file = "aiohttp-3.11.16-cp313-cp313-win_amd64.whl", hash = "sha256:42864e70a248f5f6a49fdaf417d9bc62d6e4d8ee9695b24c5916cb4bb666c802"},
+    {file = "aiohttp-3.11.16-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bbcba75fe879ad6fd2e0d6a8d937f34a571f116a0e4db37df8079e738ea95c71"},
+    {file = "aiohttp-3.11.16-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:87a6e922b2b2401e0b0cf6b976b97f11ec7f136bfed445e16384fbf6fd5e8602"},
+    {file = "aiohttp-3.11.16-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccf10f16ab498d20e28bc2b5c1306e9c1512f2840f7b6a67000a517a4b37d5ee"},
+    {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb3d0cc5cdb926090748ea60172fa8a213cec728bd6c54eae18b96040fcd6227"},
+    {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d07502cc14ecd64f52b2a74ebbc106893d9a9717120057ea9ea1fd6568a747e7"},
+    {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:776c8e959a01e5e8321f1dec77964cb6101020a69d5a94cd3d34db6d555e01f7"},
+    {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0902e887b0e1d50424112f200eb9ae3dfed6c0d0a19fc60f633ae5a57c809656"},
+    {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e87fd812899aa78252866ae03a048e77bd11b80fb4878ce27c23cade239b42b2"},
+    {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0a950c2eb8ff17361abd8c85987fd6076d9f47d040ebffce67dce4993285e973"},
+    {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:c10d85e81d0b9ef87970ecbdbfaeec14a361a7fa947118817fcea8e45335fa46"},
+    {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7951decace76a9271a1ef181b04aa77d3cc309a02a51d73826039003210bdc86"},
+    {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14461157d8426bcb40bd94deb0450a6fa16f05129f7da546090cebf8f3123b0f"},
+    {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9756d9b9d4547e091f99d554fbba0d2a920aab98caa82a8fb3d3d9bee3c9ae85"},
+    {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:87944bd16b7fe6160607f6a17808abd25f17f61ae1e26c47a491b970fb66d8cb"},
+    {file = "aiohttp-3.11.16-cp39-cp39-win32.whl", hash = "sha256:92b7ee222e2b903e0a4b329a9943d432b3767f2d5029dbe4ca59fb75223bbe2e"},
+    {file = "aiohttp-3.11.16-cp39-cp39-win_amd64.whl", hash = "sha256:17ae4664031aadfbcb34fd40ffd90976671fa0c0286e6c4113989f78bebab37a"},
+    {file = "aiohttp-3.11.16.tar.gz", hash = "sha256:16f8a2c9538c14a557b4d309ed4d0a7c60f0253e8ed7b6c9a2859a7582f8b1b8"},
 ]
 
 [package.dependencies]
@@ -621,6 +621,29 @@ files = [
     {file = "propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64"},
 ]
 
+[[package]]
+name = "psutil"
+version = "7.0.0"
+description = "Cross-platform lib for process and system monitoring in Python.  NOTE: the syntax of this script MUST be kept compatible with Python 2.7."
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"},
+    {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"},
+    {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"},
+    {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"},
+    {file = "psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"},
+    {file = "psutil-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17"},
+    {file = "psutil-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e"},
+    {file = "psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"},
+    {file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"},
+    {file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"},
+]
+
+[package.extras]
+dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"]
+test = ["pytest", "pytest-xdist", "setuptools"]
+
 [[package]]
 name = "pydantic"
 version = "1.10.18"
@@ -734,12 +757,12 @@ idna2008 = ["idna"]
 
 [[package]]
 name = "schemas"
-version = "0.0.1.dev331+7181389"
+version = "0.0.1.dev336+c6f2974"
 description = "Tools for checking if message fits expected format"
 optional = false
 python-versions = ">=3.12,<4.0"
 files = [
-    {file = "schemas-0.0.1.dev331+7181389-py3-none-any.whl", hash = "sha256:f0b51978f6a440bbc2e19b32aeec103cd5621d17902e9a4b3703d32ddd3f3ec3"},
+    {file = "schemas-0.0.1.dev336+c6f2974-py3-none-any.whl", hash = "sha256:9a2148f014bdf82e05d138629df11cb20f53799eb32dcfff14942b4b20a76cc6"},
 ]
 
 [package.dependencies]
@@ -946,4 +969,4 @@ propcache = ">=0.2.0"
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.12"
-content-hash = "eb1a2c4d9e1883bc79fd8c47f11be5b8fc25b8a75837ef1fb9beeabe12574229"
+content-hash = "82e955944afb54dfa4727975524023ccae164af25866f36871b7ae34129ccdd8"
diff --git a/beekeepy/pyproject.toml b/beekeepy/pyproject.toml
index 8f6c68c3..64d90262 100644
--- a/beekeepy/pyproject.toml
+++ b/beekeepy/pyproject.toml
@@ -23,17 +23,17 @@ source = [
 version = "0.0.0"
 
 [tool.poetry.dependencies]
-aiohttp = "3.11.14"
+python = "^3.12"
+aiohttp = "3.11.16"
 httpx = {extras = ["http2"], version = "0.23.3"}
 loguru = "0.7.2"
-python = "^3.12"
+psutil = "7.0.0"
 python-dateutil = "2.8.2"
-pydantic="1.10.18"
 requests = "2.32.3"
 setuptools = "77.0.3"
 types-setuptools = "76.0.0.20250313"
 
-schemas = "0.0.1.dev331+7181389"
+schemas = "0.0.1.dev336+c6f2974"
 
 [tool.poetry-dynamic-versioning]
 enable = true
diff --git a/hive b/hive
index 18f1d5c7..40e6bc38 160000
--- a/hive
+++ b/hive
@@ -1 +1 @@
-Subproject commit 18f1d5c753735ddba3ab85baaffc09283d84c652
+Subproject commit 40e6bc384b63fe116f370153a20c15a46888011f
diff --git a/tests/beekeepy_test/handle/api_tests/test_api_close_session.py b/tests/beekeepy_test/handle/api_tests/test_api_close_session.py
index 81168d1f..a3a99f52 100644
--- a/tests/beekeepy_test/handle/api_tests/test_api_close_session.py
+++ b/tests/beekeepy_test/handle/api_tests/test_api_close_session.py
@@ -55,11 +55,7 @@ def test_api_close_session_not_existing(create_session: bool, beekeeper: Beekeep
     """Test test_api_close_session_not_existing will test possibility of closing not existing session."""
     # ARRANGE
     if create_session:
-        assert beekeeper.settings.notification_endpoint is not None
-        beekeeper.api.create_session(
-            notifications_endpoint=beekeeper.settings.notification_endpoint.as_string(with_protocol=False),
-            salt="salt",
-        )
+        beekeeper.api.create_session(salt="salt")
 
     # ACT & ASSERT
     beekeeper.set_session_token(WRONG_TOKEN)
diff --git a/tests/beekeepy_test/handle/api_tests/test_api_create_session.py b/tests/beekeepy_test/handle/api_tests/test_api_create_session.py
index 7201ab68..b206c0d0 100644
--- a/tests/beekeepy_test/handle/api_tests/test_api_create_session.py
+++ b/tests/beekeepy_test/handle/api_tests/test_api_create_session.py
@@ -14,17 +14,11 @@ if TYPE_CHECKING:
 
 def create_session(beekeeper: Beekeeper, salt: str) -> None:
     # ARRANGE
-    assert beekeeper.settings.notification_endpoint is not None
-    notification_endpoint = beekeeper.settings.notification_endpoint.as_string(with_protocol=False)
-    message_to_check = (
-        '"id":0,"jsonrpc":"2.0","method":"beekeeper_api.create_session",'
-        f'"params":{{"notifications_endpoint":"{notification_endpoint}","salt":"{salt}"}}'
-    )
+    message_to_check = '"id":0,"jsonrpc":"2.0","method":"beekeeper_api.create_session",' f'"params":{{"salt":"{salt}"}}'
 
     # ACT
     token = (
         beekeeper.api.create_session(
-            notifications_endpoint=notification_endpoint,
             salt=salt,
         )
     ).token
diff --git a/tests/beekeepy_test/handle/commandline/application_options/test_default_values.py b/tests/beekeepy_test/handle/commandline/application_options/test_default_values.py
index 4d7f9eac..53b4c75d 100644
--- a/tests/beekeepy_test/handle/commandline/application_options/test_default_values.py
+++ b/tests/beekeepy_test/handle/commandline/application_options/test_default_values.py
@@ -18,7 +18,6 @@ def check_default_values_from_config(default_config: BeekeeperConfig) -> None:
     assert default_config.log_json_rpc == BeekeeperDefaults.DEFAULT_LOG_JSON_RPC
     assert default_config.webserver_http_endpoint == http_webserver_default()
     assert default_config.webserver_thread_pool_size == BeekeeperDefaults.DEFAULT_WEBSERVER_THREAD_POOL_SIZE
-    assert default_config.notifications_endpoint == BeekeeperDefaults.DEFAULT_NOTIFICATIONS_ENDPOINT
     assert default_config.backtrace == BeekeeperDefaults.DEFAULT_BACKTRACE
     assert default_config.export_keys_wallet == BeekeeperDefaults.DEFAULT_EXPORT_KEYS_WALLET
 
diff --git a/tests/beekeepy_test/handle/commandline/application_options/test_notifications_endpoint.py b/tests/beekeepy_test/handle/commandline/application_options/test_notifications_endpoint.py
deleted file mode 100644
index 377251ac..00000000
--- a/tests/beekeepy_test/handle/commandline/application_options/test_notifications_endpoint.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-import pytest
-
-from beekeepy.handle.runnable import BeekeeperArguments
-from beekeepy.interfaces import HttpUrl
-
-if TYPE_CHECKING:
-    from beekeepy.handle.runnable import Beekeeper
-
-
-@pytest.mark.parametrize(
-    "notifications_endpoint",
-    [HttpUrl("0.0.0.0:0", protocol="http"), HttpUrl("127.0.0.1:0", protocol="http")],
-)
-def test_notifications_endpoint(beekeeper_not_started: Beekeeper, notifications_endpoint: HttpUrl) -> None:
-    """
-    Test will check command line flag --notifications-endpoint.
-
-    In this test we will re-use built-in notification server. We will not pass a port here,
-    because built-in notification server will get free port and use it.
-
-    In order to test flag notifications-endpoint we will pass this flag, and internal
-    we will pass port that already has been taken by notification http server to beekeeper
-    executable.
-    """
-    # 1 pass notification flag
-    # 2 inside start function there is special if, that will check if we have explicitly pass
-    #   notification-endpoint flag, and append to it already taken port. This way we will
-    #   point beekeeper where to send notifications.
-
-    # ARRANGE & ACT & ASSERT
-    beekeeper_not_started.run(
-        additional_cli_arguments=BeekeeperArguments(notifications_endpoint=notifications_endpoint)
-    )
diff --git a/tests/beekeepy_test/handle/commandline/application_options/test_webserver_http_endpoint.py b/tests/beekeepy_test/handle/commandline/application_options/test_webserver_http_endpoint.py
index 7a8e0ce1..97793b53 100644
--- a/tests/beekeepy_test/handle/commandline/application_options/test_webserver_http_endpoint.py
+++ b/tests/beekeepy_test/handle/commandline/application_options/test_webserver_http_endpoint.py
@@ -16,15 +16,12 @@ if TYPE_CHECKING:
     from beekeepy.handle.runnable import Beekeeper
 
 
-def check_webserver_http_endpoint(*, nofification_endpoint: HttpUrl, webserver_http_endpoint: HttpUrl) -> None:
+def check_webserver_http_endpoint(*, webserver_http_endpoint: HttpUrl) -> None:
     """Check if beekeeper is listening on given endpoint."""
     data = {
         "jsonrpc": "2.0",
         "method": "beekeeper_api.create_session",
-        "params": {
-            "salt": "avocado",
-            "notifications_endpoint": nofification_endpoint.as_string(with_protocol=False),
-        },
+        "params": {"salt": "avocado"},
         "id": 1,
     }
 
@@ -49,8 +46,4 @@ def test_webserver_http_endpoint(beekeeper_not_started: Beekeeper, webserver_htt
     )
 
     # ASSERT
-    assert beekeeper_not_started.settings.notification_endpoint is not None
-    check_webserver_http_endpoint(
-        nofification_endpoint=beekeeper_not_started.settings.notification_endpoint,
-        webserver_http_endpoint=webserver_http_endpoint,
-    )
+    check_webserver_http_endpoint(webserver_http_endpoint=webserver_http_endpoint)
diff --git a/tests/beekeepy_test/handle/various/test_blocking_unlock.py b/tests/beekeepy_test/handle/various/test_blocking_unlock.py
index c36d4e58..821630bb 100644
--- a/tests/beekeepy_test/handle/various/test_blocking_unlock.py
+++ b/tests/beekeepy_test/handle/various/test_blocking_unlock.py
@@ -98,14 +98,8 @@ async def test_wallet_blocking_timeout(beekeeper: AsyncBeekeeper, wallet: Wallet
     assert wallets[0].name == wallet.name
 
     unlock_jsons = []
-    assert beekeeper.settings.notification_endpoint is not None
     for i in range(5):
-        session = (
-            await beekeeper.api.create_session(
-                notifications_endpoint=beekeeper.settings.notification_endpoint.as_string(with_protocol=False),
-                salt=f"salt-{i}",
-            )
-        ).token
+        session = (await beekeeper.api.create_session(salt=f"salt-{i}")).token
         unlock_json = JSONRPCRequest(
             method="beekeeper_api.unlock",
             params={
diff --git a/tests/local-tools/poetry.lock b/tests/local-tools/poetry.lock
index 87469350..9db12e17 100644
--- a/tests/local-tools/poetry.lock
+++ b/tests/local-tools/poetry.lock
@@ -13,92 +13,92 @@ files = [
 
 [[package]]
 name = "aiohttp"
-version = "3.11.14"
+version = "3.11.16"
 description = "Async http client/server framework (asyncio)"
 optional = false
 python-versions = ">=3.9"
 files = [
-    {file = "aiohttp-3.11.14-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e2bc827c01f75803de77b134afdbf74fa74b62970eafdf190f3244931d7a5c0d"},
-    {file = "aiohttp-3.11.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e365034c5cf6cf74f57420b57682ea79e19eb29033399dd3f40de4d0171998fa"},
-    {file = "aiohttp-3.11.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c32593ead1a8c6aabd58f9d7ee706e48beac796bb0cb71d6b60f2c1056f0a65f"},
-    {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4e7c7ec4146a94a307ca4f112802a8e26d969018fabed526efc340d21d3e7d0"},
-    {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8b2df9feac55043759aa89f722a967d977d80f8b5865a4153fc41c93b957efc"},
-    {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c7571f99525c76a6280f5fe8e194eeb8cb4da55586c3c61c59c33a33f10cfce7"},
-    {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b59d096b5537ec7c85954cb97d821aae35cfccce3357a2cafe85660cc6295628"},
-    {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b42dbd097abb44b3f1156b4bf978ec5853840802d6eee2784857be11ee82c6a0"},
-    {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b05774864c87210c531b48dfeb2f7659407c2dda8643104fb4ae5e2c311d12d9"},
-    {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4e2e8ef37d4bc110917d038807ee3af82700a93ab2ba5687afae5271b8bc50ff"},
-    {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e9faafa74dbb906b2b6f3eb9942352e9e9db8d583ffed4be618a89bd71a4e914"},
-    {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7e7abe865504f41b10777ac162c727af14e9f4db9262e3ed8254179053f63e6d"},
-    {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:4848ae31ad44330b30f16c71e4f586cd5402a846b11264c412de99fa768f00f3"},
-    {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2d0b46abee5b5737cb479cc9139b29f010a37b1875ee56d142aefc10686a390b"},
-    {file = "aiohttp-3.11.14-cp310-cp310-win32.whl", hash = "sha256:a0d2c04a623ab83963576548ce098baf711a18e2c32c542b62322a0b4584b990"},
-    {file = "aiohttp-3.11.14-cp310-cp310-win_amd64.whl", hash = "sha256:5409a59d5057f2386bb8b8f8bbcfb6e15505cedd8b2445db510563b5d7ea1186"},
-    {file = "aiohttp-3.11.14-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f296d637a50bb15fb6a229fbb0eb053080e703b53dbfe55b1e4bb1c5ed25d325"},
-    {file = "aiohttp-3.11.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec6cd1954ca2bbf0970f531a628da1b1338f594bf5da7e361e19ba163ecc4f3b"},
-    {file = "aiohttp-3.11.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:572def4aad0a4775af66d5a2b5923c7de0820ecaeeb7987dcbccda2a735a993f"},
-    {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c68e41c4d576cd6aa6c6d2eddfb32b2acfb07ebfbb4f9da991da26633a3db1a"},
-    {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b8bbfc8111826aa8363442c0fc1f5751456b008737ff053570f06a151650b3"},
-    {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b0a200e85da5c966277a402736a96457b882360aa15416bf104ca81e6f5807b"},
-    {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d173c0ac508a2175f7c9a115a50db5fd3e35190d96fdd1a17f9cb10a6ab09aa1"},
-    {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:413fe39fd929329f697f41ad67936f379cba06fcd4c462b62e5b0f8061ee4a77"},
-    {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65c75b14ee74e8eeff2886321e76188cbe938d18c85cff349d948430179ad02c"},
-    {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:321238a42ed463848f06e291c4bbfb3d15ba5a79221a82c502da3e23d7525d06"},
-    {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:59a05cdc636431f7ce843c7c2f04772437dd816a5289f16440b19441be6511f1"},
-    {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:daf20d9c3b12ae0fdf15ed92235e190f8284945563c4b8ad95b2d7a31f331cd3"},
-    {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:05582cb2d156ac7506e68b5eac83179faedad74522ed88f88e5861b78740dc0e"},
-    {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:12c5869e7ddf6b4b1f2109702b3cd7515667b437da90a5a4a50ba1354fe41881"},
-    {file = "aiohttp-3.11.14-cp311-cp311-win32.whl", hash = "sha256:92868f6512714efd4a6d6cb2bfc4903b997b36b97baea85f744229f18d12755e"},
-    {file = "aiohttp-3.11.14-cp311-cp311-win_amd64.whl", hash = "sha256:bccd2cb7aa5a3bfada72681bdb91637094d81639e116eac368f8b3874620a654"},
-    {file = "aiohttp-3.11.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:70ab0f61c1a73d3e0342cedd9a7321425c27a7067bebeeacd509f96695b875fc"},
-    {file = "aiohttp-3.11.14-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:602d4db80daf4497de93cb1ce00b8fc79969c0a7cf5b67bec96fa939268d806a"},
-    {file = "aiohttp-3.11.14-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a8a0d127c10b8d89e69bbd3430da0f73946d839e65fec00ae48ca7916a31948"},
-    {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9f835cdfedcb3f5947304e85b8ca3ace31eef6346d8027a97f4de5fb687534"},
-    {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8aa5c68e1e68fff7cd3142288101deb4316b51f03d50c92de6ea5ce646e6c71f"},
-    {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b512f1de1c688f88dbe1b8bb1283f7fbeb7a2b2b26e743bb2193cbadfa6f307"},
-    {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc9253069158d57e27d47a8453d8a2c5a370dc461374111b5184cf2f147a3cc3"},
-    {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b2501f1b981e70932b4a552fc9b3c942991c7ae429ea117e8fba57718cdeed0"},
-    {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:28a3d083819741592685762d51d789e6155411277050d08066537c5edc4066e6"},
-    {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0df3788187559c262922846087e36228b75987f3ae31dd0a1e5ee1034090d42f"},
-    {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e73fa341d8b308bb799cf0ab6f55fc0461d27a9fa3e4582755a3d81a6af8c09"},
-    {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:51ba80d473eb780a329d73ac8afa44aa71dfb521693ccea1dea8b9b5c4df45ce"},
-    {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8d1dd75aa4d855c7debaf1ef830ff2dfcc33f893c7db0af2423ee761ebffd22b"},
-    {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41cf0cefd9e7b5c646c2ef529c8335e7eafd326f444cc1cdb0c47b6bc836f9be"},
-    {file = "aiohttp-3.11.14-cp312-cp312-win32.whl", hash = "sha256:948abc8952aff63de7b2c83bfe3f211c727da3a33c3a5866a0e2cf1ee1aa950f"},
-    {file = "aiohttp-3.11.14-cp312-cp312-win_amd64.whl", hash = "sha256:3b420d076a46f41ea48e5fcccb996f517af0d406267e31e6716f480a3d50d65c"},
-    {file = "aiohttp-3.11.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d14e274828561db91e4178f0057a915f3af1757b94c2ca283cb34cbb6e00b50"},
-    {file = "aiohttp-3.11.14-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f30fc72daf85486cdcdfc3f5e0aea9255493ef499e31582b34abadbfaafb0965"},
-    {file = "aiohttp-3.11.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4edcbe34e6dba0136e4cabf7568f5a434d89cc9de5d5155371acda275353d228"},
-    {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a7169ded15505f55a87f8f0812c94c9412623c744227b9e51083a72a48b68a5"},
-    {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad1f2fb9fe9b585ea4b436d6e998e71b50d2b087b694ab277b30e060c434e5db"},
-    {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20412c7cc3720e47a47e63c0005f78c0c2370020f9f4770d7fc0075f397a9fb0"},
-    {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dd9766da617855f7e85f27d2bf9a565ace04ba7c387323cd3e651ac4329db91"},
-    {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:599b66582f7276ebefbaa38adf37585e636b6a7a73382eb412f7bc0fc55fb73d"},
-    {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b41693b7388324b80f9acfabd479bd1c84f0bc7e8f17bab4ecd9675e9ff9c734"},
-    {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:86135c32d06927339c8c5e64f96e4eee8825d928374b9b71a3c42379d7437058"},
-    {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:04eb541ce1e03edc1e3be1917a0f45ac703e913c21a940111df73a2c2db11d73"},
-    {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dc311634f6f28661a76cbc1c28ecf3b3a70a8edd67b69288ab7ca91058eb5a33"},
-    {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:69bb252bfdca385ccabfd55f4cd740d421dd8c8ad438ded9637d81c228d0da49"},
-    {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b86efe23684b58a88e530c4ab5b20145f102916bbb2d82942cafec7bd36a647"},
-    {file = "aiohttp-3.11.14-cp313-cp313-win32.whl", hash = "sha256:b9c60d1de973ca94af02053d9b5111c4fbf97158e139b14f1be68337be267be6"},
-    {file = "aiohttp-3.11.14-cp313-cp313-win_amd64.whl", hash = "sha256:0a29be28e60e5610d2437b5b2fed61d6f3dcde898b57fb048aa5079271e7f6f3"},
-    {file = "aiohttp-3.11.14-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:14fc03508359334edc76d35b2821832f092c8f092e4b356e74e38419dfe7b6de"},
-    {file = "aiohttp-3.11.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92007c89a8cb7be35befa2732b0b32bf3a394c1b22ef2dff0ef12537d98a7bda"},
-    {file = "aiohttp-3.11.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6d3986112e34eaa36e280dc8286b9dd4cc1a5bcf328a7f147453e188f6fe148f"},
-    {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:749f1eb10e51dbbcdba9df2ef457ec060554842eea4d23874a3e26495f9e87b1"},
-    {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:781c8bd423dcc4641298c8c5a2a125c8b1c31e11f828e8d35c1d3a722af4c15a"},
-    {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:997b57e38aa7dc6caab843c5e042ab557bc83a2f91b7bd302e3c3aebbb9042a1"},
-    {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a8b0321e40a833e381d127be993b7349d1564b756910b28b5f6588a159afef3"},
-    {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8778620396e554b758b59773ab29c03b55047841d8894c5e335f12bfc45ebd28"},
-    {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e906da0f2bcbf9b26cc2b144929e88cb3bf943dd1942b4e5af066056875c7618"},
-    {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:87f0e003fb4dd5810c7fbf47a1239eaa34cd929ef160e0a54c570883125c4831"},
-    {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7f2dadece8b85596ac3ab1ec04b00694bdd62abc31e5618f524648d18d9dd7fa"},
-    {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:fe846f0a98aa9913c2852b630cd39b4098f296e0907dd05f6c7b30d911afa4c3"},
-    {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ced66c5c6ad5bcaf9be54560398654779ec1c3695f1a9cf0ae5e3606694a000a"},
-    {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a40087b82f83bd671cbeb5f582c233d196e9653220404a798798bfc0ee189fff"},
-    {file = "aiohttp-3.11.14-cp39-cp39-win32.whl", hash = "sha256:95d7787f2bcbf7cb46823036a8d64ccfbc2ffc7d52016b4044d901abceeba3db"},
-    {file = "aiohttp-3.11.14-cp39-cp39-win_amd64.whl", hash = "sha256:22a8107896877212130c58f74e64b77f7007cb03cea8698be317272643602d45"},
-    {file = "aiohttp-3.11.14.tar.gz", hash = "sha256:d6edc538c7480fa0a3b2bdd705f8010062d74700198da55d16498e1b49549b9c"},
+    {file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb46bb0f24813e6cede6cc07b1961d4b04f331f7112a23b5e21f567da4ee50aa"},
+    {file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:54eb3aead72a5c19fad07219acd882c1643a1027fbcdefac9b502c267242f955"},
+    {file = "aiohttp-3.11.16-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:38bea84ee4fe24ebcc8edeb7b54bf20f06fd53ce4d2cc8b74344c5b9620597fd"},
+    {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0666afbe984f6933fe72cd1f1c3560d8c55880a0bdd728ad774006eb4241ecd"},
+    {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba92a2d9ace559a0a14b03d87f47e021e4fa7681dc6970ebbc7b447c7d4b7cd"},
+    {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ad1d59fd7114e6a08c4814983bb498f391c699f3c78712770077518cae63ff7"},
+    {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b88a2bf26965f2015a771381624dd4b0839034b70d406dc74fd8be4cc053e3"},
+    {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:576f5ca28d1b3276026f7df3ec841ae460e0fc3aac2a47cbf72eabcfc0f102e1"},
+    {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a2a450bcce4931b295fc0848f384834c3f9b00edfc2150baafb4488c27953de6"},
+    {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:37dcee4906454ae377be5937ab2a66a9a88377b11dd7c072df7a7c142b63c37c"},
+    {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4d0c970c0d602b1017e2067ff3b7dac41c98fef4f7472ec2ea26fd8a4e8c2149"},
+    {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:004511d3413737700835e949433536a2fe95a7d0297edd911a1e9705c5b5ea43"},
+    {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c15b2271c44da77ee9d822552201180779e5e942f3a71fb74e026bf6172ff287"},
+    {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad9509ffb2396483ceacb1eee9134724443ee45b92141105a4645857244aecc8"},
+    {file = "aiohttp-3.11.16-cp310-cp310-win32.whl", hash = "sha256:634d96869be6c4dc232fc503e03e40c42d32cfaa51712aee181e922e61d74814"},
+    {file = "aiohttp-3.11.16-cp310-cp310-win_amd64.whl", hash = "sha256:938f756c2b9374bbcc262a37eea521d8a0e6458162f2a9c26329cc87fdf06534"},
+    {file = "aiohttp-3.11.16-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8cb0688a8d81c63d716e867d59a9ccc389e97ac7037ebef904c2b89334407180"},
+    {file = "aiohttp-3.11.16-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ad1fb47da60ae1ddfb316f0ff16d1f3b8e844d1a1e154641928ea0583d486ed"},
+    {file = "aiohttp-3.11.16-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df7db76400bf46ec6a0a73192b14c8295bdb9812053f4fe53f4e789f3ea66bbb"},
+    {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc3a145479a76ad0ed646434d09216d33d08eef0d8c9a11f5ae5cdc37caa3540"},
+    {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d007aa39a52d62373bd23428ba4a2546eed0e7643d7bf2e41ddcefd54519842c"},
+    {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6ddd90d9fb4b501c97a4458f1c1720e42432c26cb76d28177c5b5ad4e332601"},
+    {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a2f451849e6b39e5c226803dcacfa9c7133e9825dcefd2f4e837a2ec5a3bb98"},
+    {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8df6612df74409080575dca38a5237282865408016e65636a76a2eb9348c2567"},
+    {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:78e6e23b954644737e385befa0deb20233e2dfddf95dd11e9db752bdd2a294d3"},
+    {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:696ef00e8a1f0cec5e30640e64eca75d8e777933d1438f4facc9c0cdf288a810"},
+    {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3538bc9fe1b902bef51372462e3d7c96fce2b566642512138a480b7adc9d508"},
+    {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3ab3367bb7f61ad18793fea2ef71f2d181c528c87948638366bf1de26e239183"},
+    {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:56a3443aca82abda0e07be2e1ecb76a050714faf2be84256dae291182ba59049"},
+    {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:61c721764e41af907c9d16b6daa05a458f066015abd35923051be8705108ed17"},
+    {file = "aiohttp-3.11.16-cp311-cp311-win32.whl", hash = "sha256:3e061b09f6fa42997cf627307f220315e313ece74907d35776ec4373ed718b86"},
+    {file = "aiohttp-3.11.16-cp311-cp311-win_amd64.whl", hash = "sha256:745f1ed5e2c687baefc3c5e7b4304e91bf3e2f32834d07baaee243e349624b24"},
+    {file = "aiohttp-3.11.16-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:911a6e91d08bb2c72938bc17f0a2d97864c531536b7832abee6429d5296e5b27"},
+    {file = "aiohttp-3.11.16-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac13b71761e49d5f9e4d05d33683bbafef753e876e8e5a7ef26e937dd766713"},
+    {file = "aiohttp-3.11.16-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fd36c119c5d6551bce374fcb5c19269638f8d09862445f85a5a48596fd59f4bb"},
+    {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d489d9778522fbd0f8d6a5c6e48e3514f11be81cb0a5954bdda06f7e1594b321"},
+    {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69a2cbd61788d26f8f1e626e188044834f37f6ae3f937bd9f08b65fc9d7e514e"},
+    {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd464ba806e27ee24a91362ba3621bfc39dbbb8b79f2e1340201615197370f7c"},
+    {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce63ae04719513dd2651202352a2beb9f67f55cb8490c40f056cea3c5c355ce"},
+    {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b00dd520d88eac9d1768439a59ab3d145065c91a8fab97f900d1b5f802895e"},
+    {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f6428fee52d2bcf96a8aa7b62095b190ee341ab0e6b1bcf50c615d7966fd45b"},
+    {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:13ceac2c5cdcc3f64b9015710221ddf81c900c5febc505dbd8f810e770011540"},
+    {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fadbb8f1d4140825069db3fedbbb843290fd5f5bc0a5dbd7eaf81d91bf1b003b"},
+    {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6a792ce34b999fbe04a7a71a90c74f10c57ae4c51f65461a411faa70e154154e"},
+    {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f4065145bf69de124accdd17ea5f4dc770da0a6a6e440c53f6e0a8c27b3e635c"},
+    {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa73e8c2656a3653ae6c307b3f4e878a21f87859a9afab228280ddccd7369d71"},
+    {file = "aiohttp-3.11.16-cp312-cp312-win32.whl", hash = "sha256:f244b8e541f414664889e2c87cac11a07b918cb4b540c36f7ada7bfa76571ea2"},
+    {file = "aiohttp-3.11.16-cp312-cp312-win_amd64.whl", hash = "sha256:23a15727fbfccab973343b6d1b7181bfb0b4aa7ae280f36fd2f90f5476805682"},
+    {file = "aiohttp-3.11.16-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a3814760a1a700f3cfd2f977249f1032301d0a12c92aba74605cfa6ce9f78489"},
+    {file = "aiohttp-3.11.16-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9b751a6306f330801665ae69270a8a3993654a85569b3469662efaad6cf5cc50"},
+    {file = "aiohttp-3.11.16-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ad497f38a0d6c329cb621774788583ee12321863cd4bd9feee1effd60f2ad133"},
+    {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca37057625693d097543bd88076ceebeb248291df9d6ca8481349efc0b05dcd0"},
+    {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5abcbba9f4b463a45c8ca8b7720891200658f6f46894f79517e6cd11f3405ca"},
+    {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f420bfe862fb357a6d76f2065447ef6f484bc489292ac91e29bc65d2d7a2c84d"},
+    {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58ede86453a6cf2d6ce40ef0ca15481677a66950e73b0a788917916f7e35a0bb"},
+    {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fdec0213244c39973674ca2a7f5435bf74369e7d4e104d6c7473c81c9bcc8c4"},
+    {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72b1b03fb4655c1960403c131740755ec19c5898c82abd3961c364c2afd59fe7"},
+    {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:780df0d837276276226a1ff803f8d0fa5f8996c479aeef52eb040179f3156cbd"},
+    {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ecdb8173e6c7aa09eee342ac62e193e6904923bd232e76b4157ac0bfa670609f"},
+    {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a6db7458ab89c7d80bc1f4e930cc9df6edee2200127cfa6f6e080cf619eddfbd"},
+    {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2540ddc83cc724b13d1838026f6a5ad178510953302a49e6d647f6e1de82bc34"},
+    {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3b4e6db8dc4879015b9955778cfb9881897339c8fab7b3676f8433f849425913"},
+    {file = "aiohttp-3.11.16-cp313-cp313-win32.whl", hash = "sha256:493910ceb2764f792db4dc6e8e4b375dae1b08f72e18e8f10f18b34ca17d0979"},
+    {file = "aiohttp-3.11.16-cp313-cp313-win_amd64.whl", hash = "sha256:42864e70a248f5f6a49fdaf417d9bc62d6e4d8ee9695b24c5916cb4bb666c802"},
+    {file = "aiohttp-3.11.16-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bbcba75fe879ad6fd2e0d6a8d937f34a571f116a0e4db37df8079e738ea95c71"},
+    {file = "aiohttp-3.11.16-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:87a6e922b2b2401e0b0cf6b976b97f11ec7f136bfed445e16384fbf6fd5e8602"},
+    {file = "aiohttp-3.11.16-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccf10f16ab498d20e28bc2b5c1306e9c1512f2840f7b6a67000a517a4b37d5ee"},
+    {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb3d0cc5cdb926090748ea60172fa8a213cec728bd6c54eae18b96040fcd6227"},
+    {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d07502cc14ecd64f52b2a74ebbc106893d9a9717120057ea9ea1fd6568a747e7"},
+    {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:776c8e959a01e5e8321f1dec77964cb6101020a69d5a94cd3d34db6d555e01f7"},
+    {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0902e887b0e1d50424112f200eb9ae3dfed6c0d0a19fc60f633ae5a57c809656"},
+    {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e87fd812899aa78252866ae03a048e77bd11b80fb4878ce27c23cade239b42b2"},
+    {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0a950c2eb8ff17361abd8c85987fd6076d9f47d040ebffce67dce4993285e973"},
+    {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:c10d85e81d0b9ef87970ecbdbfaeec14a361a7fa947118817fcea8e45335fa46"},
+    {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7951decace76a9271a1ef181b04aa77d3cc309a02a51d73826039003210bdc86"},
+    {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14461157d8426bcb40bd94deb0450a6fa16f05129f7da546090cebf8f3123b0f"},
+    {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9756d9b9d4547e091f99d554fbba0d2a920aab98caa82a8fb3d3d9bee3c9ae85"},
+    {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:87944bd16b7fe6160607f6a17808abd25f17f61ae1e26c47a491b970fb66d8cb"},
+    {file = "aiohttp-3.11.16-cp39-cp39-win32.whl", hash = "sha256:92b7ee222e2b903e0a4b329a9943d432b3767f2d5029dbe4ca59fb75223bbe2e"},
+    {file = "aiohttp-3.11.16-cp39-cp39-win_amd64.whl", hash = "sha256:17ae4664031aadfbcb34fd40ffd90976671fa0c0286e6c4113989f78bebab37a"},
+    {file = "aiohttp-3.11.16.tar.gz", hash = "sha256:16f8a2c9538c14a557b4d309ed4d0a7c60f0253e8ed7b6c9a2859a7582f8b1b8"},
 ]
 
 [package.dependencies]
@@ -176,13 +176,13 @@ files = []
 develop = true
 
 [package.dependencies]
-aiohttp = "3.11.14"
+aiohttp = "3.11.16"
 httpx = {version = "0.23.3", extras = ["http2"]}
 loguru = "0.7.2"
-pydantic = "1.10.18"
+psutil = "7.0.0"
 python-dateutil = "2.8.2"
 requests = "2.32.3"
-schemas = "0.0.1.dev331+7181389"
+schemas = "0.0.1.dev336+c6f2974"
 setuptools = "77.0.3"
 types-setuptools = "76.0.0.20250313"
 
@@ -850,6 +850,29 @@ files = [
     {file = "propcache-0.3.0.tar.gz", hash = "sha256:a8fd93de4e1d278046345f49e2238cdb298589325849b2645d4a94c53faeffc5"},
 ]
 
+[[package]]
+name = "psutil"
+version = "7.0.0"
+description = "Cross-platform lib for process and system monitoring in Python.  NOTE: the syntax of this script MUST be kept compatible with Python 2.7."
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"},
+    {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"},
+    {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"},
+    {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"},
+    {file = "psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"},
+    {file = "psutil-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17"},
+    {file = "psutil-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e"},
+    {file = "psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"},
+    {file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"},
+    {file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"},
+]
+
+[package.extras]
+dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"]
+test = ["pytest", "pytest-xdist", "setuptools"]
+
 [[package]]
 name = "pydantic"
 version = "1.10.18"
@@ -1110,12 +1133,12 @@ files = [
 
 [[package]]
 name = "schemas"
-version = "0.0.1.dev331+7181389"
+version = "0.0.1.dev336+c6f2974"
 description = "Tools for checking if message fits expected format"
 optional = false
 python-versions = ">=3.12,<4.0"
 files = [
-    {file = "schemas-0.0.1.dev331+7181389-py3-none-any.whl", hash = "sha256:f0b51978f6a440bbc2e19b32aeec103cd5621d17902e9a4b3703d32ddd3f3ec3"},
+    {file = "schemas-0.0.1.dev336+c6f2974-py3-none-any.whl", hash = "sha256:9a2148f014bdf82e05d138629df11cb20f53799eb32dcfff14942b4b20a76cc6"},
 ]
 
 [package.dependencies]
@@ -1168,6 +1191,17 @@ files = [
     {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
 ]
 
+[[package]]
+name = "types-psutil"
+version = "6.0.0.20240901"
+description = "Typing stubs for psutil"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "types-psutil-6.0.0.20240901.tar.gz", hash = "sha256:437affa76670363db9ffecfa4f153cc6900bf8a7072b3420f3bc07a593f92226"},
+    {file = "types_psutil-6.0.0.20240901-py3-none-any.whl", hash = "sha256:20af311bfb0386a018a27ae47dc952119d7c0e849ff72b6aa24fc0433afb92a6"},
+]
+
 [[package]]
 name = "types-python-dateutil"
 version = "2.8.19.14"
@@ -1389,4 +1423,4 @@ propcache = ">=0.2.0"
 [metadata]
 lock-version = "2.0"
 python-versions = "^3.12"
-content-hash = "9bedefe3bd538cea6a562fd77dc85d29aa3413a3f0900bfdfb21357dc5908f38"
+content-hash = "a7125ab9d77d3431d5076460d8ebb6d574bc96408318c0f6fa02df550b633eab"
diff --git a/tests/local-tools/pyproject.toml b/tests/local-tools/pyproject.toml
index 2f7bb48a..4c634eba 100644
--- a/tests/local-tools/pyproject.toml
+++ b/tests/local-tools/pyproject.toml
@@ -36,6 +36,7 @@ ruff = "0.6.5"
 types-python-dateutil = "2.8.19.14"
 types-pyyaml = "6.0.12.4"
 types-requests = "2.31.0.2"
+types-psutil = "6.0.0.20240901"
 
 
 [tool.mypy]
-- 
GitLab


From 45454f369fb461c9cab68e754897ee0c949d0bbd Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Sat, 22 Mar 2025 20:42:34 +0000
Subject: [PATCH 02/23] Introduce app_status_probe

---
 .../_remote_handle/app_status_probe.py        | 28 +++++++++++++++++++
 1 file changed, 28 insertions(+)
 create mode 100644 beekeepy/beekeepy/_remote_handle/app_status_probe.py

diff --git a/beekeepy/beekeepy/_remote_handle/app_status_probe.py b/beekeepy/beekeepy/_remote_handle/app_status_probe.py
new file mode 100644
index 00000000..432901fb
--- /dev/null
+++ b/beekeepy/beekeepy/_remote_handle/app_status_probe.py
@@ -0,0 +1,28 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from beekeepy._apis import AppStatusProbeSyncApiCollection
+from beekeepy._remote_handle.abc.handle import AbstractSyncHandle
+from beekeepy._remote_handle.settings import RemoteHandleSettings
+
+if TYPE_CHECKING:
+    from beekeepy._apis import SyncAppStatusApi
+    from beekeepy._remote_handle.abc.batch_handle import SyncBatchHandle
+
+
+class AppStatusProbe(AbstractSyncHandle[RemoteHandleSettings, AppStatusProbeSyncApiCollection]):
+    """Synchronous handle for probing."""
+
+    def _construct_api(self) -> AppStatusProbeSyncApiCollection:
+        return AppStatusProbeSyncApiCollection(owner=self)
+
+    @property
+    def api(self) -> SyncAppStatusApi:  # type: ignore[override]
+        return self.apis.app_status
+
+    def _target_service(self) -> str:
+        return "app_status_probe"
+
+    def batch(self, *, delay_error_on_data_access: bool = False) -> SyncBatchHandle[AppStatusProbeSyncApiCollection]:
+        raise NotImplementedError
-- 
GitLab


From 76c160b4bb55415616ec0ee2ffda9be52a186c6f Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Sat, 22 Mar 2025 20:01:19 +0000
Subject: [PATCH 03/23] Move apis to seperate sub-package

---
 beekeepy/beekeepy/_apis/__init__.py           | 30 +++++++
 beekeepy/beekeepy/_apis/abc/__init__.py       | 33 +++++++
 .../{_remote_handle => _apis}/abc/api.py      | 15 +---
 .../abc/api_collection.py                     | 11 +--
 beekeepy/beekeepy/_apis/abc/sendable.py       | 21 +++++
 .../api => _apis/abc}/session_holder.py       |  7 +-
 .../beekeepy/_apis/app_status_api/__init__.py | 19 ++++
 .../_apis/app_status_api/api_collection.py    | 32 +++++++
 .../_apis/app_status_api/async_api.py         | 12 +++
 .../beekeepy/_apis/app_status_api/sync_api.py | 12 +++
 .../api => _apis}/apply_session_token.py      |  3 +-
 .../beekeepy/_apis/beekeeper_api/__init__.py  | 14 +++
 .../_apis/beekeeper_api/api_collection.py     | 32 +++++++
 .../api => _apis/beekeeper_api}/async_api.py  | 21 ++---
 .../beekeeper_api}/beekeeper_api_commons.py   |  4 +-
 .../api => _apis/beekeeper_api}/sync_api.py   | 22 ++---
 beekeepy/beekeepy/_executable/streams.py      | 87 -------------------
 .../beekeepy/_remote_handle/api/__init__.py   |  6 --
 .../_remote_handle/api/api_collection.py      | 39 ---------
 19 files changed, 237 insertions(+), 183 deletions(-)
 create mode 100644 beekeepy/beekeepy/_apis/__init__.py
 create mode 100644 beekeepy/beekeepy/_apis/abc/__init__.py
 rename beekeepy/beekeepy/{_remote_handle => _apis}/abc/api.py (93%)
 rename beekeepy/beekeepy/{_remote_handle => _apis}/abc/api_collection.py (52%)
 create mode 100644 beekeepy/beekeepy/_apis/abc/sendable.py
 rename beekeepy/beekeepy/{_remote_handle/api => _apis/abc}/session_holder.py (88%)
 create mode 100644 beekeepy/beekeepy/_apis/app_status_api/__init__.py
 create mode 100644 beekeepy/beekeepy/_apis/app_status_api/api_collection.py
 create mode 100644 beekeepy/beekeepy/_apis/app_status_api/async_api.py
 create mode 100644 beekeepy/beekeepy/_apis/app_status_api/sync_api.py
 rename beekeepy/beekeepy/{_remote_handle/api => _apis}/apply_session_token.py (76%)
 create mode 100644 beekeepy/beekeepy/_apis/beekeeper_api/__init__.py
 create mode 100644 beekeepy/beekeepy/_apis/beekeeper_api/api_collection.py
 rename beekeepy/beekeepy/{_remote_handle/api => _apis/beekeeper_api}/async_api.py (90%)
 rename beekeepy/beekeepy/{_remote_handle/api => _apis/beekeeper_api}/beekeeper_api_commons.py (86%)
 rename beekeepy/beekeepy/{_remote_handle/api => _apis/beekeeper_api}/sync_api.py (81%)
 delete mode 100644 beekeepy/beekeepy/_executable/streams.py
 delete mode 100644 beekeepy/beekeepy/_remote_handle/api/__init__.py
 delete mode 100644 beekeepy/beekeepy/_remote_handle/api/api_collection.py

diff --git a/beekeepy/beekeepy/_apis/__init__.py b/beekeepy/beekeepy/_apis/__init__.py
new file mode 100644
index 00000000..c9d688e0
--- /dev/null
+++ b/beekeepy/beekeepy/_apis/__init__.py
@@ -0,0 +1,30 @@
+from __future__ import annotations
+
+from beekeepy._apis import abc
+from beekeepy._apis.app_status_api import (
+    AppStatusProbeAsyncApiCollection,
+    AppStatusProbeSyncApiCollection,
+    AsyncAppStatusApi,
+    SyncAppStatusApi,
+)
+from beekeepy._apis.apply_session_token import async_apply_session_token, sync_apply_session_token
+from beekeepy._apis.beekeeper_api import (
+    AsyncBeekeeperApi,
+    BeekeeperAsyncApiCollection,
+    BeekeeperSyncApiCollection,
+    SyncBeekeeperApi,
+)
+
+__all__ = [
+    "abc",
+    "AppStatusProbeAsyncApiCollection",
+    "AppStatusProbeSyncApiCollection",
+    "AsyncAppStatusApi",
+    "SyncAppStatusApi",
+    "SyncBeekeeperApi",
+    "AsyncBeekeeperApi",
+    "BeekeeperSyncApiCollection",
+    "BeekeeperAsyncApiCollection",
+    "sync_apply_session_token",
+    "async_apply_session_token",
+]
diff --git a/beekeepy/beekeepy/_apis/abc/__init__.py b/beekeepy/beekeepy/_apis/abc/__init__.py
new file mode 100644
index 00000000..558e1c1c
--- /dev/null
+++ b/beekeepy/beekeepy/_apis/abc/__init__.py
@@ -0,0 +1,33 @@
+from __future__ import annotations
+
+from beekeepy._apis.abc.api import (
+    AbstractApi,
+    AbstractAsyncApi,
+    AbstractSyncApi,
+    ApiArgumentSerialization,
+    ApiArgumentsToSerialize,
+    HandleT,
+    RegisteredApisT,
+)
+from beekeepy._apis.abc.api_collection import AbstractAsyncApiCollection, AbstractSyncApiCollection
+from beekeepy._apis.abc.sendable import (
+    AsyncSendable,
+    SyncSendable,
+)
+from beekeepy._apis.abc.session_holder import AsyncSessionHolder, SyncSessionHolder
+
+__all__ = [
+    "AbstractApi",
+    "AbstractAsyncApi",
+    "AbstractAsyncApiCollection",
+    "AbstractSyncApi",
+    "AbstractSyncApiCollection",
+    "ApiArgumentSerialization",
+    "ApiArgumentsToSerialize",
+    "AsyncSendable",
+    "AsyncSessionHolder",
+    "HandleT",
+    "RegisteredApisT",
+    "SyncSendable",
+    "SyncSessionHolder",
+]
diff --git a/beekeepy/beekeepy/_remote_handle/abc/api.py b/beekeepy/beekeepy/_apis/abc/api.py
similarity index 93%
rename from beekeepy/beekeepy/_remote_handle/abc/api.py
rename to beekeepy/beekeepy/_apis/abc/api.py
index af7bfc9e..4dcbd68c 100644
--- a/beekeepy/beekeepy/_remote_handle/abc/api.py
+++ b/beekeepy/beekeepy/_apis/abc/api.py
@@ -13,16 +13,11 @@ from typing import (
     ClassVar,
     Generic,
     ParamSpec,
-    TypeAlias,
     TypeVar,
     get_type_hints,
 )
 
-from beekeepy._remote_handle.abc.handle import (
-    AbstractAsyncHandle,
-    AbstractSyncHandle,
-)
-from beekeepy._remote_handle.batch_handle import AsyncBatchHandle, SyncBatchHandle
+from beekeepy._apis.abc.sendable import AsyncSendable, SyncSendable
 from schemas._preconfigured_base_model import PreconfiguredBaseModel
 from schemas.fields.serializable import Serializable
 from schemas.operations.representations.legacy_representation import LegacyRepresentation
@@ -34,9 +29,7 @@ if TYPE_CHECKING:
 
 
 P = ParamSpec("P")
-SyncHandleT: TypeAlias = AbstractSyncHandle[Any] | SyncBatchHandle[Any]
-AsyncHandleT: TypeAlias = AbstractAsyncHandle[Any] | AsyncBatchHandle[Any]
-HandleT = TypeVar("HandleT", bound=SyncHandleT | AsyncHandleT)
+HandleT = TypeVar("HandleT", bound=SyncSendable | AsyncSendable)
 
 RegisteredApisT = defaultdict[bool, defaultdict[str, set[str]]]
 ApiArgumentsToSerialize = tuple[tuple[Any, ...], dict[str, Any]]
@@ -121,7 +114,7 @@ class AbstractApi(ABC, Generic[HandleT]):
         self._owner = owner
 
 
-class AbstractSyncApi(AbstractApi[SyncHandleT]):
+class AbstractSyncApi(AbstractApi[SyncSendable]):
     """Base class for all apis, that provides synchronous endpoints."""
 
     def _additional_arguments_actions(
@@ -153,7 +146,7 @@ class AbstractSyncApi(AbstractApi[SyncHandleT]):
         return impl  # type: ignore[return-value]
 
 
-class AbstractAsyncApi(AbstractApi[AsyncHandleT]):
+class AbstractAsyncApi(AbstractApi[AsyncSendable]):
     """Base class for all apis, that provides asynchronous endpoints."""
 
     async def _additional_arguments_actions(
diff --git a/beekeepy/beekeepy/_remote_handle/abc/api_collection.py b/beekeepy/beekeepy/_apis/abc/api_collection.py
similarity index 52%
rename from beekeepy/beekeepy/_remote_handle/abc/api_collection.py
rename to beekeepy/beekeepy/_apis/abc/api_collection.py
index d8836883..8838e34b 100644
--- a/beekeepy/beekeepy/_remote_handle/abc/api_collection.py
+++ b/beekeepy/beekeepy/_apis/abc/api_collection.py
@@ -2,7 +2,8 @@ from __future__ import annotations
 
 from typing import Generic
 
-from beekeepy._remote_handle.abc.api import AsyncHandleT, HandleT, SyncHandleT
+from beekeepy._apis.abc.api import HandleT
+from beekeepy._apis.abc.sendable import AsyncSendable, SyncSendable
 
 
 class AbstractApiCollection(Generic[HandleT]):
@@ -12,15 +13,15 @@ class AbstractApiCollection(Generic[HandleT]):
         self._owner = owner
 
 
-class AbstractAsyncApiCollection(AbstractApiCollection[AsyncHandleT]):
+class AbstractAsyncApiCollection(AbstractApiCollection[AsyncSendable]):
     """Base class for Async Api Collections."""
 
-    def __init__(self, owner: AsyncHandleT) -> None:
+    def __init__(self, owner: AsyncSendable) -> None:
         super().__init__(owner)
 
 
-class AbstractSyncApiCollection(AbstractApiCollection[SyncHandleT]):
+class AbstractSyncApiCollection(AbstractApiCollection[SyncSendable]):
     """Base class for Sync Api Collections."""
 
-    def __init__(self, owner: SyncHandleT) -> None:
+    def __init__(self, owner: SyncSendable) -> None:
         super().__init__(owner)
diff --git a/beekeepy/beekeepy/_apis/abc/sendable.py b/beekeepy/beekeepy/_apis/abc/sendable.py
new file mode 100644
index 00000000..3f2e5ac1
--- /dev/null
+++ b/beekeepy/beekeepy/_apis/abc/sendable.py
@@ -0,0 +1,21 @@
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from schemas.jsonrpc import ExpectResultT, JSONRPCResult
+
+
+class SyncSendable(ABC):
+    @abstractmethod
+    def _send(
+        self, *, endpoint: str, params: str, expected_type: type[ExpectResultT]
+    ) -> JSONRPCResult[ExpectResultT]: ...
+
+
+class AsyncSendable(ABC):
+    @abstractmethod
+    async def _async_send(
+        self, *, endpoint: str, params: str, expected_type: type[ExpectResultT]
+    ) -> JSONRPCResult[ExpectResultT]: ...
diff --git a/beekeepy/beekeepy/_remote_handle/api/session_holder.py b/beekeepy/beekeepy/_apis/abc/session_holder.py
similarity index 88%
rename from beekeepy/beekeepy/_remote_handle/api/session_holder.py
rename to beekeepy/beekeepy/_apis/abc/session_holder.py
index 2c5b3d12..9e314b8b 100644
--- a/beekeepy/beekeepy/_remote_handle/api/session_holder.py
+++ b/beekeepy/beekeepy/_apis/abc/session_holder.py
@@ -1,8 +1,9 @@
 from __future__ import annotations
 
-from abc import abstractmethod
+from abc import ABC, abstractmethod
 from typing import Any
 
+from beekeepy._apis.abc.sendable import AsyncSendable, SyncSendable
 from schemas.apis.beekeeper_api import CreateSession
 
 __all__ = ["SyncSessionHolder", "AsyncSessionHolder"]
@@ -37,7 +38,7 @@ class SessionHolder:
         return self.__session
 
 
-class SyncSessionHolder(SessionHolder):
+class SyncSessionHolder(SyncSendable, SessionHolder, ABC):
     @abstractmethod
     def _acquire_session_token(self) -> str: ...
 
@@ -48,7 +49,7 @@ class SyncSessionHolder(SessionHolder):
         return self._check_and_return_session()
 
 
-class AsyncSessionHolder(SessionHolder):
+class AsyncSessionHolder(AsyncSendable, SessionHolder, ABC):
     @abstractmethod
     async def _acquire_session_token(self) -> str: ...
 
diff --git a/beekeepy/beekeepy/_apis/app_status_api/__init__.py b/beekeepy/beekeepy/_apis/app_status_api/__init__.py
new file mode 100644
index 00000000..58d93619
--- /dev/null
+++ b/beekeepy/beekeepy/_apis/app_status_api/__init__.py
@@ -0,0 +1,19 @@
+from __future__ import annotations
+
+from beekeepy._apis.app_status_api.api_collection import (
+    AppStatusProbeAsyncApiCollection,
+    AppStatusProbeSyncApiCollection,
+)
+from beekeepy._apis.app_status_api.async_api import (
+    AppStatusApi as AsyncAppStatusApi,
+)
+from beekeepy._apis.app_status_api.sync_api import (
+    AppStatusApi as SyncAppStatusApi,
+)
+
+__all__ = [
+    "AsyncAppStatusApi",
+    "SyncAppStatusApi",
+    "AppStatusProbeAsyncApiCollection",
+    "AppStatusProbeSyncApiCollection",
+]
diff --git a/beekeepy/beekeepy/_apis/app_status_api/api_collection.py b/beekeepy/beekeepy/_apis/app_status_api/api_collection.py
new file mode 100644
index 00000000..a61fc45d
--- /dev/null
+++ b/beekeepy/beekeepy/_apis/app_status_api/api_collection.py
@@ -0,0 +1,32 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from beekeepy._apis.abc.api_collection import AbstractAsyncApiCollection, AbstractSyncApiCollection
+from beekeepy._apis.app_status_api.async_api import AppStatusApi as AsyncAppStatusApi
+from beekeepy._apis.app_status_api.sync_api import AppStatusApi as SyncAppStatusApi
+
+if TYPE_CHECKING:
+    from beekeepy._apis.abc.sendable import AsyncSendable, SyncSendable
+
+
+class AppStatusProbeSyncApiCollection(AbstractSyncApiCollection):
+    """Beekeepers collection of available apis in async version."""
+
+    _owner: SyncSendable
+
+    def __init__(self, owner: SyncSendable) -> None:
+        super().__init__(owner)
+        self.app_status = SyncAppStatusApi(owner=self._owner)
+        self.app_status_api = self.app_status
+
+
+class AppStatusProbeAsyncApiCollection(AbstractAsyncApiCollection):
+    """Beekeepers collection of available apis in async version."""
+
+    _owner: AsyncSendable
+
+    def __init__(self, owner: AsyncSendable) -> None:
+        super().__init__(owner)
+        self.app_status = AsyncAppStatusApi(owner=self._owner)
+        self.app_status_api = self.app_status
diff --git a/beekeepy/beekeepy/_apis/app_status_api/async_api.py b/beekeepy/beekeepy/_apis/app_status_api/async_api.py
new file mode 100644
index 00000000..b6c36b7f
--- /dev/null
+++ b/beekeepy/beekeepy/_apis/app_status_api/async_api.py
@@ -0,0 +1,12 @@
+from __future__ import annotations
+
+from beekeepy._apis.abc.api import AbstractAsyncApi
+from schemas.apis import app_status_api  # noqa: TCH001
+
+
+class AppStatusApi(AbstractAsyncApi):
+    api = AbstractAsyncApi._endpoint
+
+    @api
+    async def get_app_status(self) -> app_status_api.GetAppStatus:
+        raise NotImplementedError
diff --git a/beekeepy/beekeepy/_apis/app_status_api/sync_api.py b/beekeepy/beekeepy/_apis/app_status_api/sync_api.py
new file mode 100644
index 00000000..50633a60
--- /dev/null
+++ b/beekeepy/beekeepy/_apis/app_status_api/sync_api.py
@@ -0,0 +1,12 @@
+from __future__ import annotations
+
+from beekeepy._apis.abc.api import AbstractSyncApi
+from schemas.apis import app_status_api  # noqa: TCH001
+
+
+class AppStatusApi(AbstractSyncApi):
+    api = AbstractSyncApi._endpoint
+
+    @api
+    def get_app_status(self) -> app_status_api.GetAppStatus:
+        raise NotImplementedError
diff --git a/beekeepy/beekeepy/_remote_handle/api/apply_session_token.py b/beekeepy/beekeepy/_apis/apply_session_token.py
similarity index 76%
rename from beekeepy/beekeepy/_remote_handle/api/apply_session_token.py
rename to beekeepy/beekeepy/_apis/apply_session_token.py
index e7ed3274..a440a918 100644
--- a/beekeepy/beekeepy/_remote_handle/api/apply_session_token.py
+++ b/beekeepy/beekeepy/_apis/apply_session_token.py
@@ -3,8 +3,7 @@ from __future__ import annotations
 from typing import TYPE_CHECKING
 
 if TYPE_CHECKING:
-    from beekeepy._remote_handle.abc.api import ApiArgumentsToSerialize
-    from beekeepy._remote_handle.api.session_holder import AsyncSessionHolder, SyncSessionHolder
+    from beekeepy._apis.abc import ApiArgumentsToSerialize, AsyncSessionHolder, SyncSessionHolder
 
 
 def sync_apply_session_token(owner: SyncSessionHolder, arguments: ApiArgumentsToSerialize) -> ApiArgumentsToSerialize:
diff --git a/beekeepy/beekeepy/_apis/beekeeper_api/__init__.py b/beekeepy/beekeepy/_apis/beekeeper_api/__init__.py
new file mode 100644
index 00000000..dca7d2cf
--- /dev/null
+++ b/beekeepy/beekeepy/_apis/beekeeper_api/__init__.py
@@ -0,0 +1,14 @@
+from __future__ import annotations
+
+from beekeepy._apis.beekeeper_api.api_collection import (
+    BeekeeperAsyncApiCollection,
+    BeekeeperSyncApiCollection,
+)
+from beekeepy._apis.beekeeper_api.async_api import (
+    BeekeeperApi as AsyncBeekeeperApi,
+)
+from beekeepy._apis.beekeeper_api.sync_api import (
+    BeekeeperApi as SyncBeekeeperApi,
+)
+
+__all__ = ["AsyncBeekeeperApi", "SyncBeekeeperApi", "BeekeeperAsyncApiCollection", "BeekeeperSyncApiCollection"]
diff --git a/beekeepy/beekeepy/_apis/beekeeper_api/api_collection.py b/beekeepy/beekeepy/_apis/beekeeper_api/api_collection.py
new file mode 100644
index 00000000..26744fc9
--- /dev/null
+++ b/beekeepy/beekeepy/_apis/beekeeper_api/api_collection.py
@@ -0,0 +1,32 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from beekeepy._apis.app_status_api import AppStatusProbeAsyncApiCollection, AppStatusProbeSyncApiCollection
+from beekeepy._apis.beekeeper_api.async_api import BeekeeperApi as AsyncBeekeeperApi
+from beekeepy._apis.beekeeper_api.sync_api import BeekeeperApi as SyncBeekeeperApi
+
+if TYPE_CHECKING:
+    from beekeepy._apis.abc import AsyncSessionHolder, SyncSessionHolder
+
+
+class BeekeeperAsyncApiCollection(AppStatusProbeAsyncApiCollection):
+    """Beekeepers collection of available apis in async version."""
+
+    _owner: AsyncSessionHolder
+
+    def __init__(self, owner: AsyncSessionHolder) -> None:
+        super().__init__(owner)
+        self.beekeeper = AsyncBeekeeperApi(owner=self._owner)
+        self.beekeeper_api = self.beekeeper
+
+
+class BeekeeperSyncApiCollection(AppStatusProbeSyncApiCollection):
+    """Beekeepers collection of available apis in async version."""
+
+    _owner: SyncSessionHolder
+
+    def __init__(self, owner: SyncSessionHolder) -> None:
+        super().__init__(owner)
+        self.beekeeper = SyncBeekeeperApi(owner=self._owner)
+        self.beekeeper_api = self.beekeeper
diff --git a/beekeepy/beekeepy/_remote_handle/api/async_api.py b/beekeepy/beekeepy/_apis/beekeeper_api/async_api.py
similarity index 90%
rename from beekeepy/beekeepy/_remote_handle/api/async_api.py
rename to beekeepy/beekeepy/_apis/beekeeper_api/async_api.py
index 336e20c1..ba109ee1 100644
--- a/beekeepy/beekeepy/_remote_handle/api/async_api.py
+++ b/beekeepy/beekeepy/_apis/beekeeper_api/async_api.py
@@ -1,24 +1,18 @@
 from __future__ import annotations
 
-from typing import TYPE_CHECKING
-
-from beekeepy._remote_handle.abc.api import AbstractAsyncApi, ApiArgumentsToSerialize, AsyncHandleT
-from beekeepy._remote_handle.api.apply_session_token import async_apply_session_token
-from beekeepy._remote_handle.api.beekeeper_api_commons import BeekeeperApiCommons
-from beekeepy._remote_handle.api.session_holder import AsyncSessionHolder
+from beekeepy._apis.abc import AbstractAsyncApi, ApiArgumentsToSerialize, AsyncSendable, AsyncSessionHolder
+from beekeepy._apis.apply_session_token import async_apply_session_token
+from beekeepy._apis.beekeeper_api.beekeeper_api_commons import BeekeeperApiCommons
 from schemas.apis import beekeeper_api  # noqa: TCH001
 
-if TYPE_CHECKING:
-    from beekeepy._remote_handle.beekeeper import AsyncBeekeeper, _AsyncSessionBatchHandle
-
 
-class BeekeeperApi(AbstractAsyncApi, BeekeeperApiCommons[AsyncHandleT]):
+class BeekeeperApi(AbstractAsyncApi, BeekeeperApiCommons[AsyncSendable]):
     """Set of endpoints, that allows asynchronous communication with beekeeper service."""
 
     api = AbstractAsyncApi._endpoint
-    _owner: AsyncBeekeeper | _AsyncSessionBatchHandle
+    _owner: AsyncSessionHolder
 
-    def __init__(self, owner: AsyncBeekeeper | _AsyncSessionBatchHandle) -> None:
+    def __init__(self, owner: AsyncSessionHolder) -> None:
         self._verify_is_owner_can_hold_session_token(owner=owner)
         super().__init__(owner=owner)
 
@@ -216,14 +210,13 @@ class BeekeeperApi(AbstractAsyncApi, BeekeeperApiCommons[AsyncHandleT]):
         raise NotImplementedError
 
     @api
-    async def create_session(self, *, notifications_endpoint: str = "", salt: str = "") -> beekeeper_api.CreateSession:
+    async def create_session(self, *, salt: str = "") -> beekeeper_api.CreateSession:
         """Creates session.
 
         Note:
             This is called automatically when connection with beekeeper is establish, no need to call it explicitly.
 
         Args:
-            notifications_endpoint: endpoint on which notifications of status will be broadcasted. (defaults: "")
             salt: used for generation of session token
 
         Returns:
diff --git a/beekeepy/beekeepy/_remote_handle/api/beekeeper_api_commons.py b/beekeepy/beekeepy/_apis/beekeeper_api/beekeeper_api_commons.py
similarity index 86%
rename from beekeepy/beekeepy/_remote_handle/api/beekeeper_api_commons.py
rename to beekeepy/beekeepy/_apis/beekeeper_api/beekeeper_api_commons.py
index 16b55fb4..718cc059 100644
--- a/beekeepy/beekeepy/_remote_handle/api/beekeeper_api_commons.py
+++ b/beekeepy/beekeepy/_apis/beekeeper_api/beekeeper_api_commons.py
@@ -3,10 +3,10 @@ from __future__ import annotations
 from abc import abstractmethod
 from typing import TYPE_CHECKING, Any, Generic, Protocol
 
-from beekeepy._remote_handle.abc.api import HandleT
+from beekeepy._apis.abc.api import HandleT
 
 if TYPE_CHECKING:
-    from beekeepy._remote_handle.api.session_holder import AsyncSessionHolder, SyncSessionHolder
+    from beekeepy._apis.abc import AsyncSessionHolder, SyncSessionHolder
 
 
 class CreateSessionActionProtocol(Protocol):
diff --git a/beekeepy/beekeepy/_remote_handle/api/sync_api.py b/beekeepy/beekeepy/_apis/beekeeper_api/sync_api.py
similarity index 81%
rename from beekeepy/beekeepy/_remote_handle/api/sync_api.py
rename to beekeepy/beekeepy/_apis/beekeeper_api/sync_api.py
index 1360bf52..9b236f63 100644
--- a/beekeepy/beekeepy/_remote_handle/api/sync_api.py
+++ b/beekeepy/beekeepy/_apis/beekeeper_api/sync_api.py
@@ -1,23 +1,17 @@
 from __future__ import annotations
 
-from typing import TYPE_CHECKING, cast
-
-from beekeepy._remote_handle.abc.api import AbstractSyncApi, ApiArgumentsToSerialize, SyncHandleT
-from beekeepy._remote_handle.api.apply_session_token import sync_apply_session_token
-from beekeepy._remote_handle.api.beekeeper_api_commons import BeekeeperApiCommons
-from beekeepy._remote_handle.api.session_holder import SyncSessionHolder
+from beekeepy._apis.abc import AbstractSyncApi, ApiArgumentsToSerialize, SyncSendable, SyncSessionHolder
+from beekeepy._apis.apply_session_token import sync_apply_session_token
+from beekeepy._apis.beekeeper_api.beekeeper_api_commons import BeekeeperApiCommons
 from schemas.apis import beekeeper_api  # noqa: TCH001
 
-if TYPE_CHECKING:
-    from beekeepy._remote_handle.beekeeper import Beekeeper, _SyncSessionBatchHandle
-
 
-class BeekeeperApi(AbstractSyncApi, BeekeeperApiCommons[SyncHandleT]):
+class BeekeeperApi(AbstractSyncApi, BeekeeperApiCommons[SyncSendable]):
     api = AbstractSyncApi._endpoint
 
-    _owner: Beekeeper | _SyncSessionBatchHandle
+    _owner: SyncSessionHolder
 
-    def __init__(self, owner: Beekeeper | _SyncSessionBatchHandle) -> None:
+    def __init__(self, owner: SyncSessionHolder) -> None:
         self._verify_is_owner_can_hold_session_token(owner=owner)
         super().__init__(owner=owner)
 
@@ -26,7 +20,7 @@ class BeekeeperApi(AbstractSyncApi, BeekeeperApiCommons[SyncHandleT]):
     ) -> ApiArgumentsToSerialize:
         if not self._token_required(endpoint_name):
             return super()._additional_arguments_actions(endpoint_name, arguments)
-        return sync_apply_session_token(cast(SyncSessionHolder, self._owner), arguments)
+        return sync_apply_session_token(self._owner, arguments)
 
     def _get_requires_session_holder_type(self) -> type[SyncSessionHolder]:
         return SyncSessionHolder
@@ -94,7 +88,7 @@ class BeekeeperApi(AbstractSyncApi, BeekeeperApiCommons[SyncHandleT]):
         raise NotImplementedError
 
     @api
-    def create_session(self, *, notifications_endpoint: str = "", salt: str = "") -> beekeeper_api.CreateSession:
+    def create_session(self, *, salt: str = "") -> beekeeper_api.CreateSession:
         raise NotImplementedError
 
     @api
diff --git a/beekeepy/beekeepy/_executable/streams.py b/beekeepy/beekeepy/_executable/streams.py
deleted file mode 100644
index 27b5eef1..00000000
--- a/beekeepy/beekeepy/_executable/streams.py
+++ /dev/null
@@ -1,87 +0,0 @@
-from __future__ import annotations
-
-from dataclasses import dataclass, field
-from typing import TYPE_CHECKING, TextIO, cast
-
-from beekeepy._interface.context import ContextSync
-
-if TYPE_CHECKING:
-    from pathlib import Path
-
-
-@dataclass
-class StreamRepresentation(ContextSync[TextIO]):
-    filename: str
-    dirpath: Path | None = None
-    stream: TextIO | None = None
-    _backup_count: int = 0
-    _current_filename: str | None = None
-
-    def __get_path(self) -> Path:
-        assert self.dirpath is not None, "Path is not specified"
-        if self._current_filename is None:
-            self._current_filename = self.__next_filename()
-        return self.dirpath / self._current_filename
-
-    def __get_stream(self) -> TextIO:
-        assert self.stream is not None, "Unable to get stream, as it is not opened"
-        return self.stream
-
-    def open_stream(self, mode: str = "wt") -> TextIO:
-        assert self.stream is None, "Stream is already opened"
-        self.__next_filename()
-        self.__create_user_friendly_link()
-        self.stream = cast(TextIO, self.__get_path().open(mode))
-        assert not self.stream.closed, f"Failed to open stream: `{self.stream.errors}`"
-        return self.stream
-
-    def close_stream(self) -> None:
-        self.__get_stream().close()
-        self.stream = None
-
-    def set_path_for_dir(self, dir_path: Path) -> None:
-        self.dirpath = dir_path
-
-    def _enter(self) -> TextIO:
-        return self.open_stream()
-
-    def _finally(self) -> None:
-        self.close_stream()
-
-    def __create_user_friendly_link(self) -> None:
-        assert self.dirpath is not None, "dirpath is not set"
-        user_friendly_link_dst = self.dirpath / f"{self.filename}.log"
-        user_friendly_link_dst.unlink(missing_ok=True)
-        user_friendly_link_dst.symlink_to(self.__get_path())
-
-    def __next_filename(self) -> str:
-        self._current_filename = f"{self.filename}_{self._backup_count}.log"
-        self._backup_count += 1
-        return self._current_filename
-
-    def __contains__(self, text: str) -> bool:
-        if not self.__get_path().exists():
-            return False
-
-        with self.open_stream("rt") as file:
-            for line in file:
-                if text in line:
-                    return True
-        return False
-
-
-@dataclass
-class StreamsHolder:
-    stdout: StreamRepresentation = field(default_factory=lambda: StreamRepresentation("stdout"))
-    stderr: StreamRepresentation = field(default_factory=lambda: StreamRepresentation("stderr"))
-
-    def set_paths_for_dir(self, dir_path: Path) -> None:
-        self.stdout.set_path_for_dir(dir_path)
-        self.stderr.set_path_for_dir(dir_path)
-
-    def close(self) -> None:
-        self.stdout.close_stream()
-        self.stderr.close_stream()
-
-    def __contains__(self, text: str) -> bool:
-        return (text in self.stderr) or (text in self.stdout)
diff --git a/beekeepy/beekeepy/_remote_handle/api/__init__.py b/beekeepy/beekeepy/_remote_handle/api/__init__.py
deleted file mode 100644
index 6d4be492..00000000
--- a/beekeepy/beekeepy/_remote_handle/api/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from __future__ import annotations
-
-from beekeepy._remote_handle.api.async_api import BeekeeperApi as AsyncBeekeeperApi
-from beekeepy._remote_handle.api.sync_api import BeekeeperApi as SyncBeekeeperApi
-
-__all__ = ["AsyncBeekeeperApi", "SyncBeekeeperApi"]
diff --git a/beekeepy/beekeepy/_remote_handle/api/api_collection.py b/beekeepy/beekeepy/_remote_handle/api/api_collection.py
deleted file mode 100644
index 37b2a0ff..00000000
--- a/beekeepy/beekeepy/_remote_handle/api/api_collection.py
+++ /dev/null
@@ -1,39 +0,0 @@
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-from beekeepy._remote_handle.abc.api_collection import (
-    AbstractAsyncApiCollection,
-    AbstractSyncApiCollection,
-)
-from beekeepy._remote_handle.api import AsyncBeekeeperApi, SyncBeekeeperApi
-
-if TYPE_CHECKING:
-    from beekeepy._remote_handle.beekeeper import (
-        AsyncBeekeeper,
-        Beekeeper,
-        _AsyncSessionBatchHandle,
-        _SyncSessionBatchHandle,
-    )
-
-
-class BeekeeperAsyncApiCollection(AbstractAsyncApiCollection):
-    """Beekeepers collection of available apis in async version."""
-
-    _owner: AsyncBeekeeper | _AsyncSessionBatchHandle
-
-    def __init__(self, owner: AsyncBeekeeper | _AsyncSessionBatchHandle) -> None:
-        super().__init__(owner)
-        self.beekeeper = AsyncBeekeeperApi(owner=self._owner)
-        self.beekeeper_api = self.beekeeper
-
-
-class BeekeeperSyncApiCollection(AbstractSyncApiCollection):
-    """Beekeepers collection of available apis in async version."""
-
-    _owner: Beekeeper | _SyncSessionBatchHandle
-
-    def __init__(self, owner: Beekeeper | _SyncSessionBatchHandle) -> None:
-        super().__init__(owner)
-        self.beekeeper = SyncBeekeeperApi(owner=self._owner)
-        self.beekeeper_api = self.beekeeper
-- 
GitLab


From 9c97dcca2dbfedb2912c56ba04520db6b7296400 Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Sat, 22 Mar 2025 20:03:30 +0000
Subject: [PATCH 04/23] Move utility classes and functions to separate
 sub-package

---
 beekeepy/beekeepy/_utilities/__init__.py                      | 0
 .../{_remote_handle => _utilities}/build_json_rpc_call.py     | 0
 beekeepy/beekeepy/{_interface => _utilities}/context.py       | 0
 .../{_interface => _utilities}/context_settings_updater.py    | 4 ++--
 beekeepy/beekeepy/{_interface => _utilities}/delay_guard.py   | 2 +-
 beekeepy/beekeepy/{_interface => _utilities}/error_logger.py  | 4 ++--
 beekeepy/beekeepy/{_interface => _utilities}/key_pair.py      | 0
 .../{_interface/_sanitize.py => _utilities/sanitize.py}       | 0
 .../beekeepy/{_interface => _utilities}/settings_holder.py    | 2 +-
 .../beekeepy/{_interface => _utilities}/state_invalidator.py  | 0
 beekeepy/beekeepy/{_interface => _utilities}/stopwatch.py     | 2 +-
 .../suppress_api_not_found.py}                                | 2 +-
 12 files changed, 8 insertions(+), 8 deletions(-)
 create mode 100644 beekeepy/beekeepy/_utilities/__init__.py
 rename beekeepy/beekeepy/{_remote_handle => _utilities}/build_json_rpc_call.py (100%)
 rename beekeepy/beekeepy/{_interface => _utilities}/context.py (100%)
 rename beekeepy/beekeepy/{_interface => _utilities}/context_settings_updater.py (93%)
 rename beekeepy/beekeepy/{_interface => _utilities}/delay_guard.py (97%)
 rename beekeepy/beekeepy/{_interface => _utilities}/error_logger.py (93%)
 rename beekeepy/beekeepy/{_interface => _utilities}/key_pair.py (100%)
 rename beekeepy/beekeepy/{_interface/_sanitize.py => _utilities/sanitize.py} (100%)
 rename beekeepy/beekeepy/{_interface => _utilities}/settings_holder.py (97%)
 rename beekeepy/beekeepy/{_interface => _utilities}/state_invalidator.py (100%)
 rename beekeepy/beekeepy/{_interface => _utilities}/stopwatch.py (95%)
 rename beekeepy/beekeepy/{_interface/_suppress_api_not_found.py => _utilities/suppress_api_not_found.py} (96%)

diff --git a/beekeepy/beekeepy/_utilities/__init__.py b/beekeepy/beekeepy/_utilities/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/beekeepy/beekeepy/_remote_handle/build_json_rpc_call.py b/beekeepy/beekeepy/_utilities/build_json_rpc_call.py
similarity index 100%
rename from beekeepy/beekeepy/_remote_handle/build_json_rpc_call.py
rename to beekeepy/beekeepy/_utilities/build_json_rpc_call.py
diff --git a/beekeepy/beekeepy/_interface/context.py b/beekeepy/beekeepy/_utilities/context.py
similarity index 100%
rename from beekeepy/beekeepy/_interface/context.py
rename to beekeepy/beekeepy/_utilities/context.py
diff --git a/beekeepy/beekeepy/_interface/context_settings_updater.py b/beekeepy/beekeepy/_utilities/context_settings_updater.py
similarity index 93%
rename from beekeepy/beekeepy/_interface/context_settings_updater.py
rename to beekeepy/beekeepy/_utilities/context_settings_updater.py
index a10bbc93..ca41424a 100644
--- a/beekeepy/beekeepy/_interface/context_settings_updater.py
+++ b/beekeepy/beekeepy/_utilities/context_settings_updater.py
@@ -4,12 +4,12 @@ from abc import abstractmethod
 from contextlib import contextmanager
 from typing import TYPE_CHECKING, Generic, TypeVar
 
-from beekeepy._communication.settings import CommunicationSettings
+from pydantic import BaseModel
 
 if TYPE_CHECKING:
     from collections.abc import Iterator
 
-SettingsT = TypeVar("SettingsT", bound=CommunicationSettings)
+SettingsT = TypeVar("SettingsT", bound=BaseModel)
 
 
 class ContextSettingsUpdater(Generic[SettingsT]):
diff --git a/beekeepy/beekeepy/_interface/delay_guard.py b/beekeepy/beekeepy/_utilities/delay_guard.py
similarity index 97%
rename from beekeepy/beekeepy/_interface/delay_guard.py
rename to beekeepy/beekeepy/_utilities/delay_guard.py
index 1f82840d..89754405 100644
--- a/beekeepy/beekeepy/_interface/delay_guard.py
+++ b/beekeepy/beekeepy/_utilities/delay_guard.py
@@ -5,7 +5,7 @@ import time
 from datetime import datetime, timedelta, timezone
 from typing import TYPE_CHECKING, Final
 
-from beekeepy._interface.context import ContextAsync, ContextSync
+from beekeepy._utilities.context import ContextAsync, ContextSync
 from beekeepy.exceptions import UnlockIsNotAccessibleError
 
 if TYPE_CHECKING:
diff --git a/beekeepy/beekeepy/_interface/error_logger.py b/beekeepy/beekeepy/_utilities/error_logger.py
similarity index 93%
rename from beekeepy/beekeepy/_interface/error_logger.py
rename to beekeepy/beekeepy/_utilities/error_logger.py
index 76037104..dd2872cc 100644
--- a/beekeepy/beekeepy/_interface/error_logger.py
+++ b/beekeepy/beekeepy/_utilities/error_logger.py
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING
 
 from loguru import logger as loguru_logger
 
-from beekeepy._interface.context import ContextSync
+from beekeepy._utilities.context import ContextSync
 
 if TYPE_CHECKING:
     from types import TracebackType
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
 
 
 class ErrorLogger(ContextSync[None]):
-    def __init__(self, logger: Logger | None = None, *exceptions: type[Exception]) -> None:
+    def __init__(self, logger: Logger | None = None, *exceptions: type[BaseException]) -> None:
         super().__init__()
         self.__logger = logger or loguru_logger
         self.__exception_whitelist = list(exceptions)
diff --git a/beekeepy/beekeepy/_interface/key_pair.py b/beekeepy/beekeepy/_utilities/key_pair.py
similarity index 100%
rename from beekeepy/beekeepy/_interface/key_pair.py
rename to beekeepy/beekeepy/_utilities/key_pair.py
diff --git a/beekeepy/beekeepy/_interface/_sanitize.py b/beekeepy/beekeepy/_utilities/sanitize.py
similarity index 100%
rename from beekeepy/beekeepy/_interface/_sanitize.py
rename to beekeepy/beekeepy/_utilities/sanitize.py
diff --git a/beekeepy/beekeepy/_interface/settings_holder.py b/beekeepy/beekeepy/_utilities/settings_holder.py
similarity index 97%
rename from beekeepy/beekeepy/_interface/settings_holder.py
rename to beekeepy/beekeepy/_utilities/settings_holder.py
index ae98400d..e4a3ccae 100644
--- a/beekeepy/beekeepy/_interface/settings_holder.py
+++ b/beekeepy/beekeepy/_utilities/settings_holder.py
@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
 from contextlib import contextmanager
 from typing import TYPE_CHECKING, Any
 
-from beekeepy._interface.context_settings_updater import ContextSettingsUpdater, SettingsT
+from beekeepy._utilities.context_settings_updater import ContextSettingsUpdater, SettingsT
 
 if TYPE_CHECKING:
     from collections.abc import Iterator
diff --git a/beekeepy/beekeepy/_interface/state_invalidator.py b/beekeepy/beekeepy/_utilities/state_invalidator.py
similarity index 100%
rename from beekeepy/beekeepy/_interface/state_invalidator.py
rename to beekeepy/beekeepy/_utilities/state_invalidator.py
diff --git a/beekeepy/beekeepy/_interface/stopwatch.py b/beekeepy/beekeepy/_utilities/stopwatch.py
similarity index 95%
rename from beekeepy/beekeepy/_interface/stopwatch.py
rename to beekeepy/beekeepy/_utilities/stopwatch.py
index 094f09a4..61b751fe 100644
--- a/beekeepy/beekeepy/_interface/stopwatch.py
+++ b/beekeepy/beekeepy/_utilities/stopwatch.py
@@ -4,7 +4,7 @@ from dataclasses import dataclass
 from datetime import datetime, timedelta, timezone
 from typing import Any
 
-from beekeepy._interface.context import ContextSync
+from beekeepy._utilities.context import ContextSync
 
 
 @dataclass
diff --git a/beekeepy/beekeepy/_interface/_suppress_api_not_found.py b/beekeepy/beekeepy/_utilities/suppress_api_not_found.py
similarity index 96%
rename from beekeepy/beekeepy/_interface/_suppress_api_not_found.py
rename to beekeepy/beekeepy/_utilities/suppress_api_not_found.py
index 4b7758cd..2fb37cbf 100644
--- a/beekeepy/beekeepy/_interface/_suppress_api_not_found.py
+++ b/beekeepy/beekeepy/_utilities/suppress_api_not_found.py
@@ -2,7 +2,7 @@ from __future__ import annotations
 
 from typing import TYPE_CHECKING
 
-from beekeepy._interface.context import SelfContextSync
+from beekeepy._utilities.context import SelfContextSync
 from beekeepy.exceptions import ApiNotFoundError, GroupedErrorsError
 
 if TYPE_CHECKING:
-- 
GitLab


From ba4c8f7264be9bf06f47768742bbaf58889e3299 Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Sat, 22 Mar 2025 20:04:35 +0000
Subject: [PATCH 05/23] Move url to communication and add sub-package interface

---
 beekeepy/beekeepy/_communication/__init__.py  | 28 +++++++++++++++++++
 .../{_interface => _communication}/url.py     | 11 ++++++++
 2 files changed, 39 insertions(+)
 rename beekeepy/beekeepy/{_interface => _communication}/url.py (88%)

diff --git a/beekeepy/beekeepy/_communication/__init__.py b/beekeepy/beekeepy/_communication/__init__.py
index e69de29b..f56725c6 100644
--- a/beekeepy/beekeepy/_communication/__init__.py
+++ b/beekeepy/beekeepy/_communication/__init__.py
@@ -0,0 +1,28 @@
+from __future__ import annotations
+
+from beekeepy._communication import rules
+from beekeepy._communication.abc.communicator import AbstractCommunicator
+from beekeepy._communication.abc.overseer import AbstractOverseer
+from beekeepy._communication.aiohttp_communicator import AioHttpCommunicator
+from beekeepy._communication.httpx_communicator import HttpxCommunicator
+from beekeepy._communication.overseers import CommonOverseer, StrictOverseer
+from beekeepy._communication.request_communicator import RequestCommunicator
+from beekeepy._communication.settings import CommunicationSettings
+from beekeepy._communication.url import AnyUrl, HttpUrl, P2PUrl, Url, WsUrl
+
+__all__ = [
+    "CommunicationSettings",
+    "CommonOverseer",
+    "StrictOverseer",
+    "AioHttpCommunicator",
+    "HttpxCommunicator",
+    "RequestCommunicator",
+    "AnyUrl",
+    "HttpUrl",
+    "P2PUrl",
+    "Url",
+    "WsUrl",
+    "AbstractCommunicator",
+    "AbstractOverseer",
+    "rules",
+]
diff --git a/beekeepy/beekeepy/_interface/url.py b/beekeepy/beekeepy/_communication/url.py
similarity index 88%
rename from beekeepy/beekeepy/_interface/url.py
rename to beekeepy/beekeepy/_communication/url.py
index f7a8937f..c7ce7d39 100644
--- a/beekeepy/beekeepy/_interface/url.py
+++ b/beekeepy/beekeepy/_communication/url.py
@@ -3,6 +3,8 @@ from __future__ import annotations
 from typing import Generic, Literal, TypeVar, get_args
 from urllib.parse import urlparse
 
+from typing_extensions import Self
+
 P2PProtocolT = Literal[""]
 HttpProtocolT = Literal["http", "https"]
 WsProtocolT = Literal["ws", "wss"]
@@ -18,6 +20,7 @@ class Url(Generic[ProtocolT]):
         if protocol is not None and protocol not in allowed_proto:
             raise ValueError(f"Unknown protocol: `{protocol}`, allowed: {allowed_proto}")
 
+        target_protocol: str = protocol or self._default_protocol()
         if isinstance(url, Url):
             self.__protocol: str = url.__protocol
             self.__address: str = url.__address
@@ -78,6 +81,14 @@ class Url(Generic[ProtocolT]):
         """
         return [""]
 
+    @classmethod
+    def factory(cls, *, port: int = 0, address: str = "127.0.0.1") -> Self:
+        return cls((f"{cls._default_protocol()}://" if cls._default_protocol() else "") + f"{address}:{port}")
+
+    @classmethod
+    def _default_protocol(cls) -> str:
+        return cls._allowed_protocols()[0]
+
 
 class HttpUrl(Url[HttpProtocolT]):
     @classmethod
-- 
GitLab


From 5b38d8c92a12690512527971ba30c2fc42fc11a0 Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Sat, 22 Mar 2025 20:07:37 +0000
Subject: [PATCH 06/23] Move abstract classess to abc dir in executable

---
 beekeepy/beekeepy/_executable/abc/__init__.py |  16 ++
 .../beekeepy/_executable/abc/arguments.py     | 144 ++++++++++++++++++
 .../{_interface => _executable/abc}/config.py |  50 ++++--
 .../_executable/{ => abc}/executable.py       | 109 +++++++++----
 beekeepy/beekeepy/_executable/abc/streams.py  |  87 +++++++++++
 .../_executable/arguments/arguments.py        |  64 --------
 6 files changed, 366 insertions(+), 104 deletions(-)
 create mode 100644 beekeepy/beekeepy/_executable/abc/__init__.py
 create mode 100644 beekeepy/beekeepy/_executable/abc/arguments.py
 rename beekeepy/beekeepy/{_interface => _executable/abc}/config.py (59%)
 rename beekeepy/beekeepy/_executable/{ => abc}/executable.py (63%)
 create mode 100644 beekeepy/beekeepy/_executable/abc/streams.py
 delete mode 100644 beekeepy/beekeepy/_executable/arguments/arguments.py

diff --git a/beekeepy/beekeepy/_executable/abc/__init__.py b/beekeepy/beekeepy/_executable/abc/__init__.py
new file mode 100644
index 00000000..57c9222f
--- /dev/null
+++ b/beekeepy/beekeepy/_executable/abc/__init__.py
@@ -0,0 +1,16 @@
+from __future__ import annotations
+
+from beekeepy._executable.abc.arguments import Arguments
+from beekeepy._executable.abc.config import Config
+from beekeepy._executable.abc.executable import ArgumentT, ConfigT, Executable
+from beekeepy._executable.abc.streams import StreamRepresentation, StreamsHolder
+
+__all__ = [
+    "Arguments",
+    "Executable",
+    "StreamsHolder",
+    "StreamRepresentation",
+    "Config",
+    "ArgumentT",
+    "ConfigT",
+]
diff --git a/beekeepy/beekeepy/_executable/abc/arguments.py b/beekeepy/beekeepy/_executable/abc/arguments.py
new file mode 100644
index 00000000..2fe6085a
--- /dev/null
+++ b/beekeepy/beekeepy/_executable/abc/arguments.py
@@ -0,0 +1,144 @@
+from __future__ import annotations
+
+from abc import ABC, abstractmethod
+from pathlib import Path
+from typing import TYPE_CHECKING, Any
+
+from pydantic import Field
+
+from schemas._preconfigured_base_model import PreconfiguredBaseModel
+
+if TYPE_CHECKING:
+    from typing_extensions import Self
+
+
+__all__ = ["Arguments"]
+
+
+class CliParser:
+    @classmethod
+    def parse_cli_input(cls, cli: list[str]) -> dict[str, str | list[str] | bool]:
+        ordered_cli: dict[str, str | set[str] | None] = {}
+        previous_key: str | None = None
+        for item in cli:
+            key, value = cls._preprocess_option(item)
+            if key.startswith("__"):
+                previous_key = key[2:]
+                ordered_cli[previous_key] = value
+                continue
+            if key.startswith("_"):
+                previous_key = key[1:]
+                ordered_cli[previous_key] = value
+                continue
+
+            if key in ordered_cli:
+                if isinstance((dict_value := ordered_cli[key]), set):
+                    dict_value.add(item)
+                elif ordered_cli[key] is None:
+                    assert isinstance(item, str), "parsing failed, item is not string"  # mypy check
+                    ordered_cli[key] = item
+                else:  # if ordered_cli[key] is not None and is not set
+                    assert isinstance(item, str), "parsing failed, item is not string"  # mypy check
+                    dict_value = ordered_cli[key]
+                    assert isinstance(dict_value, str), "parsing failed, dict_value is not string"  # mypy check
+                    ordered_cli[key] = {dict_value, item}
+                continue
+
+            assert (
+                previous_key is not None
+            ), "parsing failed, previous_key was not set and following argument is not prefixed"
+            ordered_cli[previous_key] = item
+            previous_key = None
+
+        return cls._convert_sets_to_lists_and_none_to_boolean(ordered_cli)
+
+    @classmethod
+    def _convert_sets_to_lists_and_none_to_boolean(
+        cls, ordered_cli: dict[str, str | set[str] | None]
+    ) -> dict[str, str | list[str] | bool]:
+        result: dict[str, str | list[str] | bool] = {}
+        for key, value in ordered_cli.items():
+            if isinstance(value, set):
+                result[key] = list(value)
+            elif value is None:
+                result[key] = True
+            else:
+                result[key] = value
+        return result
+
+    @classmethod
+    def _preprocess_option(cls, item: str) -> tuple[str, str | None]:
+        key = item
+        value = None
+        if "=" in item:
+            key, value = item.split("=")
+
+        key = key.replace("-", "_")
+        return key, value
+
+
+class Arguments(PreconfiguredBaseModel, ABC):
+    help_: bool = Field(alias="help", default=False)
+    version: bool = False
+    dump_config: bool = False
+
+    class Config:
+        arbitrary_types_allowed = True
+
+    def __convert_member_name_to_cli_value(self, member_name: str) -> str:
+        return member_name.replace("_", "-")
+
+    def __convert_member_value_to_string(self, member_value: int | str | Path | Any) -> str:
+        if isinstance(member_value, bool):
+            return ""
+        if isinstance(member_value, str):
+            return member_value
+        if isinstance(member_value, int):
+            return str(member_value)
+        if isinstance(member_value, Path):
+            return member_value.as_posix()
+        if isinstance(result := self._convert_member_value_to_string_default(member_value=member_value), str):
+            return result
+        raise TypeError("Invalid type")
+
+    @abstractmethod
+    def _convert_member_value_to_string_default(self, member_value: Any) -> str | Any: ...
+
+    def __prepare_arguments(self, pattern: str) -> list[str]:
+        data = self.dict(by_alias=True, exclude_none=True, exclude_unset=True, exclude_defaults=True)
+        cli_arguments: list[str] = []
+        for k, v in data.items():
+            cli_arguments.append(pattern.format(self.__convert_member_name_to_cli_value(k)))
+            cli_arguments.append(self.__convert_member_value_to_string(v))
+        return cli_arguments
+
+    def process(self, *, with_prefix: bool = True) -> list[str]:
+        pattern = self._generate_argument_prefix(with_prefix=with_prefix)
+        return self.__prepare_arguments(pattern)
+
+    def _generate_argument_prefix(self, *, with_prefix: bool) -> str:
+        return "--{0}" if with_prefix else "{0}"
+
+    def update_with(self, other: Self | None) -> None:
+        if other is None:
+            return
+
+        for other_name, other_value in other.dict(exclude_unset=True, exclude_defaults=True, exclude_none=True).items():
+            assert isinstance(other_name, str), "Member name has to be string"
+            setattr(self, other_name, other_value)
+
+    @classmethod
+    def parse_cli_input(cls, cli: list[str]) -> Self:
+        return cls(**CliParser.parse_cli_input(cli))
+
+    @classmethod
+    def just_get_help(cls) -> Self:
+        return cls(help_=True)
+
+    @classmethod
+    def just_get_version(cls) -> Self:
+        return cls(version=True)
+
+    @classmethod
+    def just_dump_config(cls) -> Self:
+        return cls(dump_config=True)
diff --git a/beekeepy/beekeepy/_interface/config.py b/beekeepy/beekeepy/_executable/abc/config.py
similarity index 59%
rename from beekeepy/beekeepy/_interface/config.py
rename to beekeepy/beekeepy/_executable/abc/config.py
index 0504073d..413a43c2 100644
--- a/beekeepy/beekeepy/_interface/config.py
+++ b/beekeepy/beekeepy/_executable/abc/config.py
@@ -4,9 +4,10 @@ from pathlib import Path
 from types import UnionType
 from typing import TYPE_CHECKING, Any, ClassVar, get_args
 
+from loguru import logger
 from pydantic import BaseModel
 
-from beekeepy._interface.url import Url
+from beekeepy._communication import Url
 from beekeepy.exceptions import InvalidOptionError
 
 if TYPE_CHECKING:
@@ -25,26 +26,30 @@ class Config(BaseModel):
             out_file.write("# config automatically generated by helpy\n")
             for member_name, member_value in self.__dict__.items():
                 if member_value is not None:
-                    out_file.write(
-                        f"{self._convert_member_name_to_config_name(member_name)}={self._convert_member_value_to_config_value(member_value)}\n"
-                    )
+                    if isinstance(member_value, list) and len(member_value) == 0:
+                        continue
+
+                    entry_name = self._convert_member_name_to_config_name(member_name)
+                    entry_value = self._convert_member_value_to_config_value(member_name, member_value)
+                    for value in [entry_value] if not isinstance(entry_value, list) else entry_value:
+                        out_file.write(f"{entry_name} = {value}\n")
 
     @classmethod
     def load(cls, source: Path) -> Self:
         source = source / Config.DEFAULT_FILE_NAME if source.is_dir() else source
         assert source.exists(), "Given file does not exists."
         fields = cls.__fields__
-        values_to_write = {}
+        values_to_write: dict[str, Any] = {}
         with source.open("rt", encoding="utf-8") as in_file:
             for line in in_file:
                 if (line := line.strip("\n")) and not line.startswith("#"):
                     config_name, config_value = line.split("=")
                     member_name = cls._convert_config_name_to_member_name(config_name)
                     member_type = fields[member_name].annotation
-                    if isinstance(member_type, UnionType) and get_args(member_type)[-1] is type(None):
+                    if isinstance(member_type, UnionType) and (type(None) in get_args(member_type)):
                         member_type = get_args(member_type)[0]
                     values_to_write[member_name] = cls._convert_config_value_to_member_value(
-                        config_value, expected=member_type
+                        config_value, expected=member_type, current_value=values_to_write.get(member_name)
                     )
         return cls(**values_to_write)
 
@@ -57,9 +62,9 @@ class Config(BaseModel):
         return config_name.strip().replace("-", "_")
 
     @classmethod
-    def _convert_member_value_to_config_value(cls, member_value: Any) -> str:
+    def _convert_member_value_to_config_value(cls, member_name: str, member_value: Any) -> str | list[str]:  # noqa: ARG003
         if isinstance(member_value, list):
-            return " ".join(member_value)
+            return member_value
 
         if isinstance(member_value, bool):
             return "yes" if member_value else "no"
@@ -73,8 +78,8 @@ class Config(BaseModel):
         return str(member_value)
 
     @classmethod
-    def _convert_config_value_to_member_value(  # noqa: PLR0911
-        cls, config_value: str, *, expected: type[Any]
+    def _convert_config_value_to_member_value(  # noqa: PLR0911, C901
+        cls, config_value: str, *, expected: type[Any], current_value: Any | None
     ) -> Any | None:
         config_value = config_value.strip()
         if config_value is None:
@@ -83,8 +88,21 @@ class Config(BaseModel):
         if expected == Path:
             return Path(config_value.replace('"', ""))
 
-        if expected == list[str]:
-            return config_value.split()
+        if issubclass(expected, list) or "list" in str(expected):
+            list_arg_t = get_args(expected)[0]
+            if len(get_args(list_arg_t)):  # in case of unions
+                list_arg_t = get_args(list_arg_t)[0]
+            logger.info(f"{list_arg_t=}")
+            values = [
+                cls._convert_config_value_to_member_value(value, expected=list_arg_t, current_value=None)
+                for value in config_value.split()
+            ]
+            if current_value is not None:
+                if isinstance(current_value, list):
+                    current_value.extend(values)
+                    return current_value
+                return [*values, current_value]
+            return values
 
         if expected == Url:
             return Url(config_value)
@@ -99,4 +117,10 @@ class Config(BaseModel):
 
             raise InvalidOptionError(f"Expected `yes` or `no`, got: `{config_value}`")
 
+        if "str" in str(expected):
+            return config_value.strip('"')
+
+        if isinstance(expected, type) and issubclass(expected, int | str) and hasattr(expected, "validate"):
+            return expected.validate(config_value)
+
         return expected(config_value) if expected is not None else None
diff --git a/beekeepy/beekeepy/_executable/executable.py b/beekeepy/beekeepy/_executable/abc/executable.py
similarity index 63%
rename from beekeepy/beekeepy/_executable/executable.py
rename to beekeepy/beekeepy/_executable/abc/executable.py
index 4f171554..4ba4628d 100644
--- a/beekeepy/beekeepy/_executable/executable.py
+++ b/beekeepy/beekeepy/_executable/abc/executable.py
@@ -3,17 +3,21 @@ from __future__ import annotations
 import os
 import signal
 import subprocess
-import warnings
+import time
 from abc import ABC, abstractmethod
+from contextlib import contextmanager
 from typing import TYPE_CHECKING, Any, Generic, TypeVar
 
-from beekeepy._executable.arguments.arguments import Arguments
-from beekeepy._executable.streams import StreamsHolder
-from beekeepy._interface.config import Config
-from beekeepy._interface.context import ContextSync
-from beekeepy.exceptions import BeekeeperIsNotRunningError, TimeoutReachWhileCloseError
+import psutil
+
+from beekeepy._executable.abc.arguments import Arguments
+from beekeepy._executable.abc.config import Config
+from beekeepy._executable.abc.streams import StreamsHolder
+from beekeepy._utilities.context import ContextSync
+from beekeepy.exceptions import ExecutableIsNotRunningError, TimeoutReachWhileCloseError
 
 if TYPE_CHECKING:
+    from collections.abc import Iterator
     from pathlib import Path
 
     from loguru import Logger
@@ -21,7 +25,7 @@ if TYPE_CHECKING:
 
 class Closeable(ABC):
     @abstractmethod
-    def close(self) -> None: ...
+    def close(self, timeout_secs: float = 10.0) -> None: ...
 
 
 class AutoCloser(ContextSync[None]):
@@ -70,15 +74,41 @@ class Executable(Closeable, Generic[ConfigT, ArgumentT]):
     def config(self) -> ConfigT:
         return self.__config
 
-    def run(
+    @property
+    def arguments(self) -> ArgumentT:
+        return self.__arguments
+
+    @property
+    def executable_path(self) -> Path:
+        return self.__executable_path
+
+    def _run(
         self,
         *,
         blocking: bool,
-        arguments: ArgumentT | None = None,
         environ: dict[str, str] | None = None,
         propagate_sigint: bool = True,
+        save_config: bool = True,
     ) -> AutoCloser:
-        command, environment_variables = self.__prepare(arguments=arguments, environ=environ)
+        return self.__run(
+            blocking=blocking,
+            arguments=self.arguments,
+            environ=environ,
+            propagate_sigint=propagate_sigint,
+            save_config=save_config,
+        )
+
+    def __run(
+        self,
+        *,
+        blocking: bool,
+        arguments: ArgumentT,
+        environ: dict[str, str] | None = None,
+        propagate_sigint: bool = True,
+        save_config: bool = True,
+    ) -> AutoCloser:
+        command, environment_variables = self.__prepare(arguments=arguments, environ=environ, save_config=save_config)
+        self._logger.info(f"starting `{self.__executable_path.stem}` as: `{command}`")
 
         if blocking:
             with self.__files.stdout as stdout, self.__files.stderr as stderr:
@@ -114,9 +144,11 @@ class Executable(Closeable, Generic[ConfigT, ArgumentT]):
         return result.decode().strip()
 
     def __prepare(
-        self, arguments: ArgumentT | None, environ: dict[str, str] | None
+        self,
+        arguments: ArgumentT,
+        environ: dict[str, str] | None,
+        save_config: bool = True,  # noqa: FBT001, FBT002
     ) -> tuple[list[str], dict[str, str]]:
-        arguments = arguments or self.__arguments
         environ = environ or {}
 
         self.__working_directory.mkdir(exist_ok=True)
@@ -127,7 +159,8 @@ class Executable(Closeable, Generic[ConfigT, ArgumentT]):
 
         environment_variables = dict(os.environ)
         environment_variables.update(environ)
-        self.config.save(self.working_directory)
+        if save_config:
+            self.config.save(self.working_directory)
 
         return command, environment_variables
 
@@ -136,7 +169,7 @@ class Executable(Closeable, Generic[ConfigT, ArgumentT]):
 
     def detach(self) -> int:
         if self.__process is None:
-            raise BeekeeperIsNotRunningError
+            raise ExecutableIsNotRunningError
         pid = self.pid
         self.__process = None
         self.__files.close()
@@ -158,16 +191,6 @@ class Executable(Closeable, Generic[ConfigT, ArgumentT]):
             self.__process = None
             self.__files.close()
 
-    def __warn_if_pid_files_exists(self) -> None:
-        if self.__pid_files_exists():
-            warnings.warn(
-                f"PID file has not been removed, malfunction may occur. Working directory: {self.working_directory}",
-                stacklevel=2,
-            )
-
-    def __pid_files_exists(self) -> bool:
-        return len(list(self.working_directory.glob("*.pid"))) > 0
-
     def is_running(self) -> bool:
         if not self.__process:
             return False
@@ -177,6 +200,17 @@ class Executable(Closeable, Generic[ConfigT, ArgumentT]):
     def log_has_phrase(self, text: str) -> bool:
         return text in self.__files
 
+    @contextmanager
+    def restore_arguments(self, new_arguments: ArgumentT | None) -> Iterator[None]:
+        __backup = self.__arguments
+        self.__arguments = new_arguments or self.__arguments
+        try:
+            yield
+        except:  # noqa: TRY302 # https://docs.python.org/3/library/contextlib.html#contextlib.contextmanager
+            raise
+        finally:
+            self.__arguments = __backup
+
     @abstractmethod
     def _construct_config(self) -> ConfigT: ...
 
@@ -184,9 +218,20 @@ class Executable(Closeable, Generic[ConfigT, ArgumentT]):
     def _construct_arguments(self) -> ArgumentT: ...
 
     def generate_default_config(self) -> ConfigT:
-        path_to_config = self.working_directory / (Config.DEFAULT_FILE_NAME)
-        self.run(blocking=True, arguments=self.__arguments.just_dump_config())
-        temp_path_to_file = path_to_config.rename(Config.DEFAULT_FILE_NAME + ".tmp")
+        if not self.working_directory.exists():
+            self.working_directory.mkdir(parents=True)
+        orig_path_to_config: Path | None = None
+        path_to_config = self.working_directory / Config.DEFAULT_FILE_NAME
+        if path_to_config.exists():
+            orig_path_to_config = path_to_config.rename(
+                path_to_config.with_suffix(".ini.orig")
+            )  # temporary move it to not interfere with config generation
+        arguments = self._construct_arguments()
+        arguments.dump_config = True
+        self.__run(blocking=True, arguments=arguments, save_config=False)
+        temp_path_to_file = path_to_config.rename(path_to_config.with_suffix(".ini.tmp"))
+        if orig_path_to_config is not None:
+            orig_path_to_config.rename(path_to_config)
         return self.config.load(temp_path_to_file)
 
     def get_help_text(self) -> str:
@@ -194,3 +239,13 @@ class Executable(Closeable, Generic[ConfigT, ArgumentT]):
 
     def version(self) -> str:
         return self.run_and_get_output(arguments=self.__arguments.just_get_version())
+
+    def reserved_ports(self, *, timeout_seconds: int = 10) -> list[int]:
+        assert self.is_running(), "Cannot obtain reserved ports for not started executable"
+        start = time.perf_counter()
+        while start + timeout_seconds >= time.perf_counter():
+            connections = psutil.net_connections("inet4")
+            reserved_ports = [connection.laddr[1] for connection in connections if connection.pid == self.pid]  # type: ignore[misc]
+            if reserved_ports:
+                return reserved_ports
+        raise TimeoutError
diff --git a/beekeepy/beekeepy/_executable/abc/streams.py b/beekeepy/beekeepy/_executable/abc/streams.py
new file mode 100644
index 00000000..1e615284
--- /dev/null
+++ b/beekeepy/beekeepy/_executable/abc/streams.py
@@ -0,0 +1,87 @@
+from __future__ import annotations
+
+from dataclasses import dataclass, field
+from typing import TYPE_CHECKING, TextIO, cast
+
+from beekeepy._utilities.context import ContextSync
+
+if TYPE_CHECKING:
+    from pathlib import Path
+
+
+@dataclass
+class StreamRepresentation(ContextSync[TextIO]):
+    filename: str
+    dirpath: Path | None = None
+    stream: TextIO | None = None
+    _backup_count: int = 0
+    _current_filename: str | None = None
+
+    def __get_path(self) -> Path:
+        assert self.dirpath is not None, "Path is not specified"
+        if self._current_filename is None:
+            self._current_filename = self.__next_filename()
+        return self.dirpath / self._current_filename
+
+    def __get_stream(self) -> TextIO:
+        assert self.stream is not None, "Unable to get stream, as it is not opened"
+        return self.stream
+
+    def open_stream(self, mode: str = "wt") -> TextIO:
+        assert self.stream is None, "Stream is already opened"
+        self.__next_filename()
+        self.__create_user_friendly_link()
+        self.stream = cast(TextIO, self.__get_path().open(mode))
+        assert not self.stream.closed, f"Failed to open stream: `{self.stream.errors}`"
+        return self.stream
+
+    def close_stream(self) -> None:
+        self.__get_stream().close()
+        self.stream = None
+
+    def set_path_for_dir(self, dir_path: Path) -> None:
+        self.dirpath = dir_path
+
+    def _enter(self) -> TextIO:
+        return self.open_stream()
+
+    def _finally(self) -> None:
+        self.close_stream()
+
+    def __create_user_friendly_link(self) -> None:
+        assert self.dirpath is not None, "dirpath is not set"
+        user_friendly_link_dst = self.dirpath / f"{self.filename}.log"
+        user_friendly_link_dst.unlink(missing_ok=True)
+        user_friendly_link_dst.symlink_to(self.__get_path())
+
+    def __next_filename(self) -> str:
+        self._current_filename = f"{self.filename}_{self._backup_count}.log"
+        self._backup_count += 1
+        return self._current_filename
+
+    def __contains__(self, text: str) -> bool:
+        if not self.__get_path().exists():
+            return False
+
+        with self.open_stream("rt") as file:
+            for line in file:
+                if text in line:
+                    return True
+        return False
+
+
+@dataclass
+class StreamsHolder:
+    stdout: StreamRepresentation = field(default_factory=lambda: StreamRepresentation("stdout"))
+    stderr: StreamRepresentation = field(default_factory=lambda: StreamRepresentation("stderr"))
+
+    def set_paths_for_dir(self, dir_path: Path) -> None:
+        self.stdout.set_path_for_dir(dir_path)
+        self.stderr.set_path_for_dir(dir_path)
+
+    def close(self) -> None:
+        self.stdout.close_stream()
+        self.stderr.close_stream()
+
+    def __contains__(self, text: str) -> bool:
+        return (text in self.stderr) or (text in self.stdout)
diff --git a/beekeepy/beekeepy/_executable/arguments/arguments.py b/beekeepy/beekeepy/_executable/arguments/arguments.py
deleted file mode 100644
index 4a248218..00000000
--- a/beekeepy/beekeepy/_executable/arguments/arguments.py
+++ /dev/null
@@ -1,64 +0,0 @@
-from __future__ import annotations
-
-from abc import ABC, abstractmethod
-from pathlib import Path
-from typing import TYPE_CHECKING, Any
-
-from pydantic import Field
-
-from schemas._preconfigured_base_model import PreconfiguredBaseModel
-
-if TYPE_CHECKING:
-    from typing_extensions import Self
-
-
-class Arguments(PreconfiguredBaseModel, ABC):
-    help_: bool = Field(alias="help", default=False)
-    version: bool = False
-    dump_config: bool = False
-
-    class Config:
-        arbitrary_types_allowed = True
-
-    def __convert_member_name_to_cli_value(self, member_name: str) -> str:
-        return member_name.replace("_", "-")
-
-    def __convert_member_value_to_string(self, member_value: int | str | Path | Any) -> str:
-        if isinstance(member_value, bool):
-            return ""
-        if isinstance(member_value, str):
-            return member_value
-        if isinstance(member_value, int):
-            return str(member_value)
-        if isinstance(member_value, Path):
-            return member_value.as_posix()
-        if isinstance(result := self._convert_member_value_to_string_default(member_value=member_value), str):
-            return result
-        raise TypeError("Invalid type")
-
-    @abstractmethod
-    def _convert_member_value_to_string_default(self, member_value: Any) -> str | Any: ...
-
-    def __prepare_arguments(self, pattern: str) -> list[str]:
-        data = self.dict(by_alias=True, exclude_none=True, exclude_unset=True, exclude_defaults=True)
-        cli_arguments: list[str] = []
-        for k, v in data.items():
-            cli_arguments.append(pattern.format(self.__convert_member_name_to_cli_value(k)))
-            cli_arguments.append(self.__convert_member_value_to_string(v))
-        return cli_arguments
-
-    def process(self, *, with_prefix: bool = True) -> list[str]:
-        pattern = "--{0}" if with_prefix else "{0}"
-        return self.__prepare_arguments(pattern)
-
-    @classmethod
-    def just_get_help(cls) -> Self:
-        return cls(help_=True)
-
-    @classmethod
-    def just_get_version(cls) -> Self:
-        return cls(version=True)
-
-    @classmethod
-    def just_dump_config(cls) -> Self:
-        return cls(dump_config=True)
-- 
GitLab


From 1ade7449bd9ac0ffa4d4bde0db082396f75e1455 Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Sat, 22 Mar 2025 20:10:06 +0000
Subject: [PATCH 07/23] Move batch_handle to abc dir in _remote_handle

---
 beekeepy/beekeepy/_remote_handle/{ => abc}/batch_handle.py | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename beekeepy/beekeepy/_remote_handle/{ => abc}/batch_handle.py (100%)

diff --git a/beekeepy/beekeepy/_remote_handle/batch_handle.py b/beekeepy/beekeepy/_remote_handle/abc/batch_handle.py
similarity index 100%
rename from beekeepy/beekeepy/_remote_handle/batch_handle.py
rename to beekeepy/beekeepy/_remote_handle/abc/batch_handle.py
-- 
GitLab


From bc2199ab55a1ac905499c6e31bcb21d2e2d2b463 Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Sat, 22 Mar 2025 20:19:11 +0000
Subject: [PATCH 08/23] Adjust imports to moved classes and functions

---
 beekeepy/beekeepy/__init__.py                 | 25 +++++-----
 .../_communication/abc/communicator.py        |  8 ++--
 .../beekeepy/_communication/abc/overseer.py   | 11 ++---
 beekeepy/beekeepy/_communication/abc/rules.py |  2 +-
 .../_communication/aiohttp_communicator.py    |  4 +-
 .../_communication/httpx_communicator.py      |  4 +-
 .../_communication/request_communicator.py    |  4 +-
 beekeepy/beekeepy/_communication/settings.py  |  2 +-
 .../_executable/arguments/__init__.py         |  0
 .../arguments/beekeeper_arguments.py          | 46 -------------------
 .../beekeepy/_executable/beekeeper_config.py  |  4 +-
 .../_executable/beekeeper_executable.py       | 38 +++++++++------
 beekeepy/beekeepy/_executable/defaults.py     |  2 +-
 beekeepy/beekeepy/_interface/__init__.py      | 22 +++++++++
 beekeepy/beekeepy/_interface/abc/__init__.py  | 23 ++++++++++
 .../_interface/abc/asynchronous/beekeeper.py  |  8 ++--
 .../_interface/abc/asynchronous/session.py    |  2 +-
 .../_interface/abc/asynchronous/wallet.py     |  2 +-
 .../beekeepy/_interface/abc/packed_object.py  | 19 +++-----
 .../_interface/abc/synchronous/beekeeper.py   |  8 ++--
 .../_interface/abc/synchronous/session.py     |  2 +-
 .../_interface/abc/synchronous/wallet.py      |  2 +-
 .../_interface/asynchronous/beekeeper.py      |  7 ++-
 .../_interface/asynchronous/session.py        |  3 +-
 .../_interface/asynchronous/wallet.py         |  2 +
 .../_interface/synchronous/beekeeper.py       |  5 +-
 .../_interface/synchronous/session.py         |  3 +-
 .../beekeepy/_interface/synchronous/wallet.py |  2 +
 .../beekeepy/_interface/wallets_common.py     | 21 +++++----
 beekeepy/beekeepy/_remote_handle/__init__.py  | 30 ++++++++++++
 .../_remote_handle/abc/batch_handle.py        | 12 ++---
 .../beekeepy/_remote_handle/abc/handle.py     | 44 ++++++++++--------
 beekeepy/beekeepy/_remote_handle/beekeeper.py | 35 +++++++++-----
 .../beekeepy/_runnable_handle/__init__.py     | 21 +++++++--
 .../beekeepy/_runnable_handle/settings.py     |  5 +-
 beekeepy/beekeepy/exceptions/base.py          |  6 +--
 beekeepy/beekeepy/exceptions/common.py        | 10 +---
 beekeepy/beekeepy/exceptions/overseer.py      |  4 +-
 beekeepy/beekeepy/handle/remote.py            | 26 +++++++----
 beekeepy/beekeepy/handle/runnable.py          | 23 ++--------
 beekeepy/beekeepy/interfaces.py               | 30 ++++++------
 .../patterns/generate_help_pattern.py         |  7 ++-
 .../application_options/test_backtrace.py     |  9 ++--
 .../test_export_keys_wallet.py                |  2 +-
 .../application_options/test_log_json_rpc.py  |  2 +-
 .../test_unlock_timeout.py                    |  2 +-
 .../application_options/test_wallet_dir.py    |  2 +-
 .../test_webserver_http_endpoint.py           |  4 +-
 .../test_webserver_thread_pool_size.py        |  2 +-
 .../handle/commandline/conftest.py            |  8 +++-
 .../handle/storage/test_storage.py            |  9 ++--
 .../handle/various/test_blocking_unlock.py    |  4 +-
 tests/beekeepy_test/interface/test_setup.py   | 16 ++++---
 .../interface/test_standalone_beekeeper.py    |  6 +--
 tests/conftest.py                             |  2 +-
 .../beekeepy/account_credentials.py           |  2 +-
 .../local_tools/beekeepy/network.py           | 11 ++---
 57 files changed, 342 insertions(+), 273 deletions(-)
 delete mode 100644 beekeepy/beekeepy/_executable/arguments/__init__.py
 delete mode 100644 beekeepy/beekeepy/_executable/arguments/beekeeper_arguments.py

diff --git a/beekeepy/beekeepy/__init__.py b/beekeepy/beekeepy/__init__.py
index e416f596..b5402dc7 100644
--- a/beekeepy/beekeepy/__init__.py
+++ b/beekeepy/beekeepy/__init__.py
@@ -1,16 +1,19 @@
 from __future__ import annotations
 
-from beekeepy._interface.abc.asynchronous.beekeeper import Beekeeper as AsyncBeekeeper
-from beekeepy._interface.abc.asynchronous.session import Session as AsyncSession
-from beekeepy._interface.abc.asynchronous.wallet import UnlockedWallet as AsyncUnlockedWallet
-from beekeepy._interface.abc.asynchronous.wallet import Wallet as AsyncWallet
-from beekeepy._interface.abc.packed_object import PackedAsyncBeekeeper, PackedSyncBeekeeper
-from beekeepy._interface.abc.synchronous.beekeeper import Beekeeper
-from beekeepy._interface.abc.synchronous.session import Session
-from beekeepy._interface.abc.synchronous.wallet import UnlockedWallet, Wallet
-from beekeepy._remote_handle.settings import Settings as RemoteHandleSettings
-from beekeepy._runnable_handle.close_already_running_beekeeper import close_already_running_beekeeper
-from beekeepy._runnable_handle.settings import Settings
+from beekeepy._interface import InterfaceSettings as Settings
+from beekeepy._interface.abc import (
+    AsyncBeekeeper,
+    AsyncSession,
+    AsyncUnlockedWallet,
+    AsyncWallet,
+    Beekeeper,
+    PackedAsyncBeekeeper,
+    PackedSyncBeekeeper,
+    Session,
+    UnlockedWallet,
+    Wallet,
+)
+from beekeepy._runnable_handle import close_already_running_beekeeper
 
 __all__ = [
     "AsyncBeekeeper",
diff --git a/beekeepy/beekeepy/_communication/abc/communicator.py b/beekeepy/beekeepy/_communication/abc/communicator.py
index 2dae1791..d72446d9 100644
--- a/beekeepy/beekeepy/_communication/abc/communicator.py
+++ b/beekeepy/beekeepy/_communication/abc/communicator.py
@@ -7,13 +7,13 @@ from threading import Thread
 from typing import TYPE_CHECKING, Any, Awaitable
 
 from beekeepy._communication.settings import CommunicationSettings
-from beekeepy._interface.settings_holder import SharedSettingsHolder
-from beekeepy._interface.stopwatch import Stopwatch
+from beekeepy._utilities.settings_holder import SharedSettingsHolder
+from beekeepy._utilities.stopwatch import Stopwatch
 from beekeepy.exceptions import CommunicationError, TimeoutExceededError, UnknownDecisionPathError
 
 if TYPE_CHECKING:
-    from beekeepy._interface.stopwatch import StopwatchResult
-    from beekeepy._interface.url import HttpUrl
+    from beekeepy._communication.url import HttpUrl
+    from beekeepy._utilities.stopwatch import StopwatchResult
 
 
 class AbstractCommunicator(SharedSettingsHolder[CommunicationSettings], ABC):
diff --git a/beekeepy/beekeepy/_communication/abc/overseer.py b/beekeepy/beekeepy/_communication/abc/overseer.py
index 30d75296..756eae86 100644
--- a/beekeepy/beekeepy/_communication/abc/overseer.py
+++ b/beekeepy/beekeepy/_communication/abc/overseer.py
@@ -5,19 +5,16 @@ from abc import ABC, abstractmethod
 from enum import IntEnum
 from typing import TYPE_CHECKING, Any, Callable, ClassVar, Sequence
 
-from beekeepy._interface.context import SelfContextSync
-from beekeepy.exceptions import (
-    GroupedErrorsError,
-    UnknownDecisionPathError,
-)
+from beekeepy._utilities.context import SelfContextSync
+from beekeepy.exceptions import GroupedErrorsError, Json, UnknownDecisionPathError
 
 if TYPE_CHECKING:
     from types import TracebackType
 
     from beekeepy._communication.abc.communicator import AbstractCommunicator
     from beekeepy._communication.abc.rules import Rules, RulesClassifier
-    from beekeepy._interface.url import HttpUrl
-    from beekeepy.exceptions import Json, OverseerError
+    from beekeepy._communication.url import HttpUrl
+    from beekeepy.exceptions import OverseerError
 
 
 __all__ = ["AbstractOverseer"]
diff --git a/beekeepy/beekeepy/_communication/abc/rules.py b/beekeepy/beekeepy/_communication/abc/rules.py
index a87c0c5e..2234ae29 100644
--- a/beekeepy/beekeepy/_communication/abc/rules.py
+++ b/beekeepy/beekeepy/_communication/abc/rules.py
@@ -5,7 +5,7 @@ from dataclasses import dataclass
 from typing import TYPE_CHECKING, Sequence
 
 if TYPE_CHECKING:
-    from beekeepy._interface.url import HttpUrl
+    from beekeepy._communication.url import HttpUrl
     from beekeepy.exceptions import Json, OverseerError
 
 
diff --git a/beekeepy/beekeepy/_communication/aiohttp_communicator.py b/beekeepy/beekeepy/_communication/aiohttp_communicator.py
index f20e9442..07095537 100644
--- a/beekeepy/beekeepy/_communication/aiohttp_communicator.py
+++ b/beekeepy/beekeepy/_communication/aiohttp_communicator.py
@@ -13,8 +13,8 @@ from beekeepy.exceptions import CommunicationError, UnknownDecisionPathError
 
 if TYPE_CHECKING:
     from beekeepy._communication.settings import CommunicationSettings
-    from beekeepy._interface.stopwatch import StopwatchResult
-    from beekeepy._interface.url import HttpUrl
+    from beekeepy._communication.url import HttpUrl
+    from beekeepy._utilities.stopwatch import StopwatchResult
 
 
 class AioHttpCommunicator(AbstractCommunicator):
diff --git a/beekeepy/beekeepy/_communication/httpx_communicator.py b/beekeepy/beekeepy/_communication/httpx_communicator.py
index 30aaf85b..2162cc4b 100644
--- a/beekeepy/beekeepy/_communication/httpx_communicator.py
+++ b/beekeepy/beekeepy/_communication/httpx_communicator.py
@@ -11,8 +11,8 @@ from beekeepy.exceptions import CommunicationError
 
 if TYPE_CHECKING:
     from beekeepy._communication.settings import CommunicationSettings
-    from beekeepy._interface.stopwatch import StopwatchResult
-    from beekeepy._interface.url import HttpUrl
+    from beekeepy._communication.url import HttpUrl
+    from beekeepy._utilities.stopwatch import StopwatchResult
 
 ClientTypes = httpx.AsyncClient | httpx.Client
 
diff --git a/beekeepy/beekeepy/_communication/request_communicator.py b/beekeepy/beekeepy/_communication/request_communicator.py
index 258b494b..71b73d06 100644
--- a/beekeepy/beekeepy/_communication/request_communicator.py
+++ b/beekeepy/beekeepy/_communication/request_communicator.py
@@ -11,8 +11,8 @@ from beekeepy.exceptions import CommunicationError
 
 if TYPE_CHECKING:
     from beekeepy._communication.settings import CommunicationSettings
-    from beekeepy._interface.stopwatch import StopwatchResult
-    from beekeepy._interface.url import HttpUrl
+    from beekeepy._communication.url import HttpUrl
+    from beekeepy._utilities.stopwatch import StopwatchResult
 
 
 class RequestCommunicator(AbstractCommunicator):
diff --git a/beekeepy/beekeepy/_communication/settings.py b/beekeepy/beekeepy/_communication/settings.py
index ee6feaef..3a0d3a7c 100644
--- a/beekeepy/beekeepy/_communication/settings.py
+++ b/beekeepy/beekeepy/_communication/settings.py
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, cast
 
 from pydantic import BaseModel, Field
 
-from beekeepy._interface.url import Url
+from beekeepy._communication.url import Url
 
 if TYPE_CHECKING:
     from collections.abc import Callable
diff --git a/beekeepy/beekeepy/_executable/arguments/__init__.py b/beekeepy/beekeepy/_executable/arguments/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/beekeepy/beekeepy/_executable/arguments/beekeeper_arguments.py b/beekeepy/beekeepy/_executable/arguments/beekeeper_arguments.py
deleted file mode 100644
index 525fde4f..00000000
--- a/beekeepy/beekeepy/_executable/arguments/beekeeper_arguments.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from __future__ import annotations
-
-from dataclasses import dataclass
-from pathlib import Path
-from typing import Any, ClassVar, Literal
-
-from beekeepy._executable.arguments.arguments import Arguments
-from beekeepy._interface.url import HttpUrl
-
-
-class BeekeeperArgumentsDefaults:
-    DEFAULT_BACKTRACE: ClassVar[Literal["yes", "no"]] = "yes"
-    DEFAULT_DATA_DIR: ClassVar[Path] = Path.cwd()
-    DEFAULT_EXPORT_KEYS_WALLET: ClassVar[ExportKeysWalletParams | None] = None
-    DEFAULT_LOG_JSON_RPC: ClassVar[Path | None] = None
-    DEFAULT_NOTIFICATIONS_ENDPOINT: ClassVar[HttpUrl | None] = None
-    DEFAULT_UNLOCK_TIMEOUT: ClassVar[int] = 900
-    DEFAULT_UNLOCK_INTERVAL: ClassVar[int] = 500
-    DEFAULT_WALLET_DIR: ClassVar[Path] = Path.cwd()
-    DEFAULT_WEBSERVER_THREAD_POOL_SIZE: ClassVar[int] = 32
-    DEFAULT_WEBSERVER_HTTP_ENDPOINT: ClassVar[HttpUrl | None] = None
-
-
-@dataclass
-class ExportKeysWalletParams:
-    wallet_name: str
-    wallet_password: str
-
-
-class BeekeeperArguments(Arguments):
-    backtrace: Literal["yes", "no"] | None = BeekeeperArgumentsDefaults.DEFAULT_BACKTRACE
-    data_dir: Path = BeekeeperArgumentsDefaults.DEFAULT_DATA_DIR
-    export_keys_wallet: ExportKeysWalletParams | None = BeekeeperArgumentsDefaults.DEFAULT_EXPORT_KEYS_WALLET
-    log_json_rpc: Path | None = BeekeeperArgumentsDefaults.DEFAULT_LOG_JSON_RPC
-    notifications_endpoint: HttpUrl | None = BeekeeperArgumentsDefaults.DEFAULT_NOTIFICATIONS_ENDPOINT
-    unlock_timeout: int | None = BeekeeperArgumentsDefaults.DEFAULT_UNLOCK_TIMEOUT
-    wallet_dir: Path | None = BeekeeperArgumentsDefaults.DEFAULT_WALLET_DIR
-    webserver_thread_pool_size: int | None = BeekeeperArgumentsDefaults.DEFAULT_WEBSERVER_THREAD_POOL_SIZE
-    webserver_http_endpoint: HttpUrl | None = BeekeeperArgumentsDefaults.DEFAULT_WEBSERVER_HTTP_ENDPOINT
-
-    def _convert_member_value_to_string_default(self, member_value: Any) -> str | Any:
-        if isinstance(member_value, HttpUrl):
-            return member_value.as_string(with_protocol=False)
-        if isinstance(member_value, ExportKeysWalletParams):
-            return f'["{member_value.wallet_name}","{member_value.wallet_password}"]'
-        return member_value
diff --git a/beekeepy/beekeepy/_executable/beekeeper_config.py b/beekeepy/beekeepy/_executable/beekeeper_config.py
index 3aea8726..8dd7fdac 100644
--- a/beekeepy/beekeepy/_executable/beekeeper_config.py
+++ b/beekeepy/beekeepy/_executable/beekeeper_config.py
@@ -4,9 +4,9 @@ from pathlib import Path  # noqa: TCH003
 
 from pydantic import Field
 
+from beekeepy._communication import HttpUrl, WsUrl
+from beekeepy._executable.abc.config import Config
 from beekeepy._executable.defaults import BeekeeperDefaults, ExportKeysWalletParams
-from beekeepy._interface.config import Config
-from beekeepy._interface.url import HttpUrl, WsUrl
 
 
 def http_webserver_default() -> HttpUrl:
diff --git a/beekeepy/beekeepy/_executable/beekeeper_executable.py b/beekeepy/beekeepy/_executable/beekeeper_executable.py
index 39e94765..e710c947 100644
--- a/beekeepy/beekeepy/_executable/beekeeper_executable.py
+++ b/beekeepy/beekeepy/_executable/beekeeper_executable.py
@@ -6,23 +6,19 @@ import tempfile
 from pathlib import Path
 from typing import TYPE_CHECKING
 
-from beekeepy._executable.arguments.beekeeper_arguments import BeekeeperArguments, ExportKeysWalletParams
+from beekeepy._executable.abc.executable import AutoCloser, Executable
+from beekeepy._executable.beekeeper_arguments import BeekeeperArguments, ExportKeysWalletParams
 from beekeepy._executable.beekeeper_config import BeekeeperConfig
 from beekeepy._executable.beekeeper_executable_discovery import get_beekeeper_binary_path
-from beekeepy._executable.executable import Executable
-from beekeepy._interface.key_pair import KeyPair
-from beekeepy._interface.url import HttpUrl
-from beekeepy._runnable_handle.settings import Settings
+from beekeepy._utilities.key_pair import KeyPair
 
 if TYPE_CHECKING:
     from loguru import Logger
 
 
 class BeekeeperExecutable(Executable[BeekeeperConfig, BeekeeperArguments]):
-    def __init__(self, settings: Settings, logger: Logger) -> None:
-        super().__init__(
-            settings.binary_path or get_beekeeper_binary_path(), settings.ensured_working_directory, logger
-        )
+    def __init__(self, executable_path: Path | None, working_directory: Path, logger: Logger) -> None:
+        super().__init__(executable_path or get_beekeeper_binary_path(), working_directory, logger)
 
     def _construct_config(self) -> BeekeeperConfig:
         config = BeekeeperConfig(wallet_dir=self.working_directory)
@@ -40,16 +36,19 @@ class BeekeeperExecutable(Executable[BeekeeperConfig, BeekeeperArguments]):
             wallet_file_name = f"{wallet_name}.wallet"
             shutil.copyfile(self.working_directory / wallet_file_name, tempdir_path / wallet_file_name)
             bk = BeekeeperExecutable(
-                settings=Settings(binary_path=get_beekeeper_binary_path(), working_directory=self.working_directory),
+                executable_path=self.executable_path,
+                working_directory=self.working_directory,
                 logger=self._logger,
             )
-            bk.run(
-                blocking=True,
-                arguments=BeekeeperArguments(
+            with bk.restore_arguments(
+                BeekeeperArguments(
                     data_dir=tempdir_path,
                     export_keys_wallet=ExportKeysWalletParams(wallet_name=wallet_name, wallet_password=wallet_password),
-                ),
-            )
+                )
+            ):
+                bk.run(
+                    blocking=True,
+                )
 
         keys_path = bk.working_directory / f"{wallet_name}.keys"
         if extract_to is not None:
@@ -58,3 +57,12 @@ class BeekeeperExecutable(Executable[BeekeeperConfig, BeekeeperArguments]):
 
         with keys_path.open("r") as file:
             return [KeyPair(**obj) for obj in json.load(file)]
+
+    def run(
+        self,
+        *,
+        blocking: bool,
+        environ: dict[str, str] | None = None,
+        propagate_sigint: bool = True,
+    ) -> AutoCloser:
+        return self._run(blocking=blocking, environ=environ, propagate_sigint=propagate_sigint)
diff --git a/beekeepy/beekeepy/_executable/defaults.py b/beekeepy/beekeepy/_executable/defaults.py
index 955fcf0a..894b2a37 100644
--- a/beekeepy/beekeepy/_executable/defaults.py
+++ b/beekeepy/beekeepy/_executable/defaults.py
@@ -6,7 +6,7 @@ from typing import ClassVar
 
 from pydantic import BaseModel
 
-from beekeepy._interface.url import HttpUrl  # noqa: TCH001
+from beekeepy._communication import HttpUrl  # noqa: TCH001
 
 
 @dataclass
diff --git a/beekeepy/beekeepy/_interface/__init__.py b/beekeepy/beekeepy/_interface/__init__.py
index e69de29b..9802b2ef 100644
--- a/beekeepy/beekeepy/_interface/__init__.py
+++ b/beekeepy/beekeepy/_interface/__init__.py
@@ -0,0 +1,22 @@
+from __future__ import annotations
+
+from beekeepy._interface import abc
+from beekeepy._interface.asynchronous.beekeeper import Beekeeper as AsyncBeekeper
+from beekeepy._interface.asynchronous.session import Session as AsyncSession
+from beekeepy._interface.asynchronous.wallet import UnlockedWallet as AsyncUnlockedWallet
+from beekeepy._interface.asynchronous.wallet import Wallet as AsyncWallet
+from beekeepy._interface.synchronous.beekeeper import Beekeeper
+from beekeepy._interface.synchronous.session import Session
+from beekeepy._interface.synchronous.wallet import UnlockedWallet, Wallet
+
+__all__ = [
+    "abc",
+    "AsyncBeekeper",
+    "AsyncSession",
+    "AsyncUnlockedWallet",
+    "AsyncWallet",
+    "Beekeeper",
+    "Session",
+    "UnlockedWallet",
+    "Wallet",
+]
diff --git a/beekeepy/beekeepy/_interface/abc/__init__.py b/beekeepy/beekeepy/_interface/abc/__init__.py
index e69de29b..b73bcd06 100644
--- a/beekeepy/beekeepy/_interface/abc/__init__.py
+++ b/beekeepy/beekeepy/_interface/abc/__init__.py
@@ -0,0 +1,23 @@
+from __future__ import annotations
+
+from beekeepy._interface.abc.asynchronous.beekeeper import Beekeeper as AsyncBeekeeper
+from beekeepy._interface.abc.asynchronous.session import Session as AsyncSession
+from beekeepy._interface.abc.asynchronous.wallet import UnlockedWallet as AsyncUnlockedWallet
+from beekeepy._interface.abc.asynchronous.wallet import Wallet as AsyncWallet
+from beekeepy._interface.abc.packed_object import PackedAsyncBeekeeper, PackedSyncBeekeeper
+from beekeepy._interface.abc.synchronous.beekeeper import Beekeeper
+from beekeepy._interface.abc.synchronous.session import Session
+from beekeepy._interface.abc.synchronous.wallet import UnlockedWallet, Wallet
+
+__all__ = [
+    "AsyncBeekeeper",
+    "AsyncSession",
+    "AsyncUnlockedWallet",
+    "AsyncWallet",
+    "Beekeeper",
+    "PackedAsyncBeekeeper",
+    "PackedSyncBeekeeper",
+    "Session",
+    "UnlockedWallet",
+    "Wallet",
+]
diff --git a/beekeepy/beekeepy/_interface/abc/asynchronous/beekeeper.py b/beekeepy/beekeepy/_interface/abc/asynchronous/beekeeper.py
index f1509340..ddda2365 100644
--- a/beekeepy/beekeepy/_interface/abc/asynchronous/beekeeper.py
+++ b/beekeepy/beekeepy/_interface/abc/asynchronous/beekeeper.py
@@ -3,15 +3,15 @@ from __future__ import annotations
 from abc import ABC, abstractmethod
 from typing import TYPE_CHECKING, cast
 
-from beekeepy._communication.settings import CommunicationSettings
+from beekeepy._communication import CommunicationSettings
 from beekeepy._interface.context import ContextAsync
-from beekeepy._interface.context_settings_updater import ContextSettingsUpdater
-from beekeepy._runnable_handle.settings import Settings
+from beekeepy._utilities.context import ContextAsync
+from beekeepy._utilities.context_settings_updater import ContextSettingsUpdater
 
 if TYPE_CHECKING:
+    from beekeepy._communication import HttpUrl
     from beekeepy._interface.abc.asynchronous.session import Session
     from beekeepy._interface.abc.packed_object import PackedAsyncBeekeeper
-    from beekeepy._interface.url import HttpUrl
 
 
 class Beekeeper(ContextAsync["Beekeeper"], ContextSettingsUpdater[CommunicationSettings], ABC):
diff --git a/beekeepy/beekeepy/_interface/abc/asynchronous/session.py b/beekeepy/beekeepy/_interface/abc/asynchronous/session.py
index befad86f..8d2c2e93 100644
--- a/beekeepy/beekeepy/_interface/abc/asynchronous/session.py
+++ b/beekeepy/beekeepy/_interface/abc/asynchronous/session.py
@@ -3,7 +3,7 @@ from __future__ import annotations
 from abc import ABC, abstractmethod
 from typing import TYPE_CHECKING, TypeAlias, overload
 
-from beekeepy._interface.context import ContextAsync
+from beekeepy._utilities.context import ContextAsync
 
 if TYPE_CHECKING:
     from beekeepy._interface.abc.asynchronous.wallet import UnlockedWallet, Wallet
diff --git a/beekeepy/beekeepy/_interface/abc/asynchronous/wallet.py b/beekeepy/beekeepy/_interface/abc/asynchronous/wallet.py
index f251972e..27726dde 100644
--- a/beekeepy/beekeepy/_interface/abc/asynchronous/wallet.py
+++ b/beekeepy/beekeepy/_interface/abc/asynchronous/wallet.py
@@ -3,8 +3,8 @@ from __future__ import annotations
 from abc import ABC, abstractmethod
 from typing import TYPE_CHECKING
 
-from beekeepy._interface.context import ContextAsync
 from beekeepy._interface.wallets_common import ContainsWalletName
+from beekeepy._utilities.context import ContextAsync
 
 if TYPE_CHECKING:
     from datetime import datetime
diff --git a/beekeepy/beekeepy/_interface/abc/packed_object.py b/beekeepy/beekeepy/_interface/abc/packed_object.py
index c92f7ddb..5ba364b8 100644
--- a/beekeepy/beekeepy/_interface/abc/packed_object.py
+++ b/beekeepy/beekeepy/_interface/abc/packed_object.py
@@ -2,17 +2,17 @@ from __future__ import annotations
 
 from typing import TYPE_CHECKING, Generic, Protocol, TypeVar
 
-from beekeepy._interface.settings_holder import UniqueSettingsHolder
-from beekeepy._runnable_handle.settings import Settings
+from beekeepy._interface.settings import InterfaceSettings
+from beekeepy._utilities.settings_holder import UniqueSettingsHolder
 
 if TYPE_CHECKING:
+    from beekeepy._communication import HttpUrl
     from beekeepy._interface.abc.asynchronous.beekeeper import (
         Beekeeper as AsynchronousBeekeeperInterface,
     )
     from beekeepy._interface.abc.synchronous.beekeeper import (
         Beekeeper as SynchronousBeekeeperInterface,
     )
-    from beekeepy._interface.url import HttpUrl
 
 __all__ = [
     "PackedSyncBeekeeper",
@@ -21,27 +21,22 @@ __all__ = [
 
 
 class _SyncRemoteFactoryCallable(Protocol):
-    def __call__(self, *, url_or_settings: HttpUrl | Settings) -> SynchronousBeekeeperInterface: ...
+    def __call__(self, *, url_or_settings: HttpUrl | InterfaceSettings) -> SynchronousBeekeeperInterface: ...
 
 
 class _AsyncRemoteFactoryCallable(Protocol):
-    async def __call__(self, *, url_or_settings: HttpUrl | Settings) -> AsynchronousBeekeeperInterface: ...
+    async def __call__(self, *, url_or_settings: HttpUrl | InterfaceSettings) -> AsynchronousBeekeeperInterface: ...
 
 
 FactoryT = TypeVar("FactoryT", bound=_SyncRemoteFactoryCallable | _AsyncRemoteFactoryCallable)
 
 
-class Packed(UniqueSettingsHolder[Settings], Generic[FactoryT]):
+class Packed(UniqueSettingsHolder[InterfaceSettings], Generic[FactoryT]):
     """Allows to transfer beekeeper handle to other process."""
 
-    def __init__(self, settings: Settings, unpack_factory: FactoryT) -> None:
+    def __init__(self, settings: InterfaceSettings, unpack_factory: FactoryT) -> None:
         super().__init__(settings=settings)
         self._unpack_factory = unpack_factory
-        self._prepare_settings_for_packing()
-
-    def _prepare_settings_for_packing(self) -> None:
-        with self.update_settings() as settings:
-            settings.notification_endpoint = None
 
 
 class PackedSyncBeekeeper(Packed[_SyncRemoteFactoryCallable]):
diff --git a/beekeepy/beekeepy/_interface/abc/synchronous/beekeeper.py b/beekeepy/beekeepy/_interface/abc/synchronous/beekeeper.py
index 22febafd..ac6dc8ed 100644
--- a/beekeepy/beekeepy/_interface/abc/synchronous/beekeeper.py
+++ b/beekeepy/beekeepy/_interface/abc/synchronous/beekeeper.py
@@ -3,15 +3,15 @@ from __future__ import annotations
 from abc import ABC, abstractmethod
 from typing import TYPE_CHECKING, cast
 
-from beekeepy._communication.settings import CommunicationSettings
+from beekeepy._communication import CommunicationSettings
 from beekeepy._interface.context import ContextSync
-from beekeepy._interface.context_settings_updater import ContextSettingsUpdater
-from beekeepy._runnable_handle.settings import Settings
+from beekeepy._utilities.context import ContextSync
+from beekeepy._utilities.context_settings_updater import ContextSettingsUpdater
 
 if TYPE_CHECKING:
+    from beekeepy._communication import HttpUrl
     from beekeepy._interface.abc.packed_object import PackedSyncBeekeeper
     from beekeepy._interface.abc.synchronous.session import Session
-    from beekeepy._interface.url import HttpUrl
 
 
 class Beekeeper(ContextSync["Beekeeper"], ContextSettingsUpdater[CommunicationSettings], ABC):
diff --git a/beekeepy/beekeepy/_interface/abc/synchronous/session.py b/beekeepy/beekeepy/_interface/abc/synchronous/session.py
index 458b9640..2292259b 100644
--- a/beekeepy/beekeepy/_interface/abc/synchronous/session.py
+++ b/beekeepy/beekeepy/_interface/abc/synchronous/session.py
@@ -3,7 +3,7 @@ from __future__ import annotations
 from abc import ABC, abstractmethod
 from typing import TYPE_CHECKING, TypeAlias, overload
 
-from beekeepy._interface.context import ContextSync
+from beekeepy._utilities.context import ContextSync
 
 if TYPE_CHECKING:
     from beekeepy._interface.abc.synchronous.wallet import UnlockedWallet, Wallet
diff --git a/beekeepy/beekeepy/_interface/abc/synchronous/wallet.py b/beekeepy/beekeepy/_interface/abc/synchronous/wallet.py
index 31f07bec..51895086 100644
--- a/beekeepy/beekeepy/_interface/abc/synchronous/wallet.py
+++ b/beekeepy/beekeepy/_interface/abc/synchronous/wallet.py
@@ -3,8 +3,8 @@ from __future__ import annotations
 from abc import ABC, abstractmethod
 from typing import TYPE_CHECKING
 
-from beekeepy._interface.context import ContextSync
 from beekeepy._interface.wallets_common import ContainsWalletName
+from beekeepy._utilities.context import ContextSync
 from schemas.fields.basic import PublicKey
 
 if TYPE_CHECKING:
diff --git a/beekeepy/beekeepy/_interface/asynchronous/beekeeper.py b/beekeepy/beekeepy/_interface/asynchronous/beekeeper.py
index 799922a0..b54837bb 100644
--- a/beekeepy/beekeepy/_interface/asynchronous/beekeeper.py
+++ b/beekeepy/beekeepy/_interface/asynchronous/beekeeper.py
@@ -10,8 +10,8 @@ from beekeepy._interface.asynchronous.session import Session
 from beekeepy._interface.delay_guard import AsyncDelayGuard
 from beekeepy._interface.state_invalidator import StateInvalidator
 from beekeepy._remote_handle.beekeeper import AsyncBeekeeper as AsynchronousRemoteBeekeeperHandle
-from beekeepy._runnable_handle.beekeeper import AsyncBeekeeper as AsynchronousBeekeeperHandle
-from beekeepy._runnable_handle.settings import Settings
+from beekeepy._utilities.delay_guard import AsyncDelayGuard
+from beekeepy._utilities.state_invalidator import StateInvalidator
 from beekeepy.exceptions import (
     DetachRemoteBeekeeperError,
     InvalidatedStateByClosingBeekeeperError,
@@ -19,11 +19,10 @@ from beekeepy.exceptions import (
 )
 
 if TYPE_CHECKING:
-    from beekeepy._communication.settings import CommunicationSettings
+    from beekeepy._communication import CommunicationSettings, HttpUrl
     from beekeepy._interface.abc.asynchronous.session import (
         Session as SessionInterface,
     )
-    from beekeepy._interface.url import HttpUrl
 
 
 class Beekeeper(BeekeeperInterface, StateInvalidator):
diff --git a/beekeepy/beekeepy/_interface/asynchronous/session.py b/beekeepy/beekeepy/_interface/asynchronous/session.py
index 6e808935..35e3704e 100644
--- a/beekeepy/beekeepy/_interface/asynchronous/session.py
+++ b/beekeepy/beekeepy/_interface/asynchronous/session.py
@@ -9,8 +9,8 @@ from beekeepy._interface.asynchronous.wallet import (
     UnlockedWallet,
     Wallet,
 )
-from beekeepy._interface.state_invalidator import StateInvalidator
 from beekeepy._interface.validators import validate_digest, validate_public_keys, validate_timeout
+from beekeepy._utilities.state_invalidator import StateInvalidator
 from beekeepy.exceptions import (
     InvalidatedStateByClosingSessionError,
     InvalidWalletError,
@@ -29,6 +29,7 @@ if TYPE_CHECKING:
     )
     from beekeepy._interface.delay_guard import AsyncDelayGuard
     from beekeepy._remote_handle.beekeeper import AsyncBeekeeper as AsynchronousRemoteBeekeeperHandle
+    from beekeepy._utilities.delay_guard import AsyncDelayGuard
     from schemas.apis.beekeeper_api import GetInfo
     from schemas.fields.basic import PublicKey
     from schemas.fields.hex import Signature
diff --git a/beekeepy/beekeepy/_interface/asynchronous/wallet.py b/beekeepy/beekeepy/_interface/asynchronous/wallet.py
index 761cb085..5cfac6dc 100644
--- a/beekeepy/beekeepy/_interface/asynchronous/wallet.py
+++ b/beekeepy/beekeepy/_interface/asynchronous/wallet.py
@@ -13,6 +13,8 @@ from beekeepy._interface.validators import validate_digest, validate_private_key
 from beekeepy._interface.wallets_common import WalletCommons
 from beekeepy._remote_handle.beekeeper import AsyncBeekeeper as AsyncRemoteBeekeeper
 from beekeepy._runnable_handle.callbacks_protocol import AsyncWalletLocked
+from beekeepy._runnable_handle import AsyncWalletLocked
+from beekeepy._utilities.delay_guard import AsyncDelayGuard
 from beekeepy.exceptions import (
     InvalidPasswordError,
     InvalidPrivateKeyError,
diff --git a/beekeepy/beekeepy/_interface/synchronous/beekeeper.py b/beekeepy/beekeepy/_interface/synchronous/beekeeper.py
index 3761fe38..d6042276 100644
--- a/beekeepy/beekeepy/_interface/synchronous/beekeeper.py
+++ b/beekeepy/beekeepy/_interface/synchronous/beekeeper.py
@@ -12,6 +12,8 @@ from beekeepy._interface.synchronous.session import Session
 from beekeepy._remote_handle.beekeeper import Beekeeper as SynchronousRemoteBeekeeperHandle
 from beekeepy._runnable_handle.beekeeper import Beekeeper as SynchronousBeekeeperHandle
 from beekeepy._runnable_handle.settings import Settings
+from beekeepy._utilities.delay_guard import SyncDelayGuard
+from beekeepy._utilities.state_invalidator import StateInvalidator
 from beekeepy.exceptions import (
     DetachRemoteBeekeeperError,
     InvalidatedStateByClosingBeekeeperError,
@@ -19,11 +21,10 @@ from beekeepy.exceptions import (
 )
 
 if TYPE_CHECKING:
-    from beekeepy._communication.settings import CommunicationSettings
+    from beekeepy._communication import CommunicationSettings, HttpUrl
     from beekeepy._interface.abc.synchronous.session import (
         Session as SessionInterface,
     )
-    from beekeepy._interface.url import HttpUrl
 
 
 class Beekeeper(BeekeeperInterface, StateInvalidator):
diff --git a/beekeepy/beekeepy/_interface/synchronous/session.py b/beekeepy/beekeepy/_interface/synchronous/session.py
index 3e616a05..9fab6de8 100644
--- a/beekeepy/beekeepy/_interface/synchronous/session.py
+++ b/beekeepy/beekeepy/_interface/synchronous/session.py
@@ -4,12 +4,12 @@ from typing import TYPE_CHECKING, Any, Callable
 
 from beekeepy._interface.abc.synchronous.session import Password
 from beekeepy._interface.abc.synchronous.session import Session as SessionInterface
-from beekeepy._interface.state_invalidator import StateInvalidator
 from beekeepy._interface.synchronous.wallet import (
     UnlockedWallet,
     Wallet,
 )
 from beekeepy._interface.validators import validate_digest, validate_public_keys, validate_timeout
+from beekeepy._utilities.state_invalidator import StateInvalidator
 from beekeepy.exceptions import (
     InvalidatedStateByClosingSessionError,
     InvalidWalletError,
@@ -28,6 +28,7 @@ if TYPE_CHECKING:
     )
     from beekeepy._interface.delay_guard import SyncDelayGuard
     from beekeepy._remote_handle.beekeeper import Beekeeper as SyncRemoteBeekeeper
+    from beekeepy._utilities.delay_guard import SyncDelayGuard
     from schemas.apis.beekeeper_api import GetInfo
     from schemas.fields.basic import PublicKey
     from schemas.fields.hex import Signature
diff --git a/beekeepy/beekeepy/_interface/synchronous/wallet.py b/beekeepy/beekeepy/_interface/synchronous/wallet.py
index d23ae316..5fbbb202 100644
--- a/beekeepy/beekeepy/_interface/synchronous/wallet.py
+++ b/beekeepy/beekeepy/_interface/synchronous/wallet.py
@@ -13,6 +13,8 @@ from beekeepy._interface.validators import validate_private_keys, validate_publi
 from beekeepy._interface.wallets_common import WalletCommons
 from beekeepy._remote_handle.beekeeper import Beekeeper as SyncRemoteBeekeeper
 from beekeepy._runnable_handle.callbacks_protocol import SyncWalletLocked
+from beekeepy._runnable_handle import SyncWalletLocked
+from beekeepy._utilities.delay_guard import SyncDelayGuard
 from beekeepy.exceptions import (
     InvalidPasswordError,
     InvalidPrivateKeyError,
diff --git a/beekeepy/beekeepy/_interface/wallets_common.py b/beekeepy/beekeepy/_interface/wallets_common.py
index 2750323c..328655f4 100644
--- a/beekeepy/beekeepy/_interface/wallets_common.py
+++ b/beekeepy/beekeepy/_interface/wallets_common.py
@@ -6,11 +6,12 @@ from asyncio import iscoroutinefunction
 from functools import wraps
 from typing import TYPE_CHECKING, Any, Generic, NoReturn, ParamSpec, TypeVar, overload
 
-from beekeepy._interface.delay_guard import AsyncDelayGuard, SyncDelayGuard
-from beekeepy._interface.state_invalidator import StateInvalidator
-from beekeepy._remote_handle.beekeeper import AsyncBeekeeper as AsyncRemoteBeekeeper
-from beekeepy._remote_handle.beekeeper import Beekeeper as SyncRemoteBeekeeper
-from beekeepy._runnable_handle.callbacks_protocol import AsyncWalletLocked, SyncWalletLocked
+from beekeepy._interface.settings import InterfaceSettings
+from beekeepy._remote_handle import AsyncBeekeeperTemplate as AsyncRemoteBeekeeper
+from beekeepy._remote_handle import BeekeeperTemplate as SyncRemoteBeekeeper
+from beekeepy._runnable_handle import AsyncWalletLocked, SyncWalletLocked
+from beekeepy._utilities.delay_guard import AsyncDelayGuard, SyncDelayGuard
+from beekeepy._utilities.state_invalidator import StateInvalidator
 from beekeepy.exceptions import WalletIsLockedError
 
 if TYPE_CHECKING:
@@ -20,7 +21,9 @@ if TYPE_CHECKING:
 
 P = ParamSpec("P")
 ResultT = TypeVar("ResultT")
-BeekeeperT = TypeVar("BeekeeperT", bound=SyncRemoteBeekeeper | AsyncRemoteBeekeeper)
+BeekeeperT = TypeVar(
+    "BeekeeperT", bound=SyncRemoteBeekeeper[InterfaceSettings] | AsyncRemoteBeekeeper[InterfaceSettings]
+)
 CallbackT = TypeVar("CallbackT", bound=AsyncWalletLocked | SyncWalletLocked)
 GuardT = TypeVar("GuardT", bound=SyncDelayGuard | AsyncDelayGuard)
 
@@ -132,7 +135,9 @@ class WalletCommons(ContainsWalletName, StateInvalidator, Generic[BeekeeperT, Ca
 
             @wraps(wrapped_function)
             async def async_impl(*args: P.args, **kwrags: P.kwargs) -> ResultT:
-                this: WalletCommons[AsyncRemoteBeekeeper, AsyncWalletLocked, AsyncDelayGuard] = args[0]  # type: ignore[assignment]
+                this: WalletCommons[AsyncRemoteBeekeeper[InterfaceSettings], AsyncWalletLocked, AsyncDelayGuard] = args[
+                    0
+                ]  # type: ignore[assignment]
                 await this._async_call_callback_if_locked(wallet_name=this.name, token=this.session_token)
                 return await wrapped_function(*args, **kwrags)  # type: ignore[no-any-return]
 
@@ -140,7 +145,7 @@ class WalletCommons(ContainsWalletName, StateInvalidator, Generic[BeekeeperT, Ca
 
         @wraps(wrapped_function)
         def sync_impl(*args: P.args, **kwrags: P.kwargs) -> ResultT:
-            this: WalletCommons[SyncRemoteBeekeeper, SyncWalletLocked, SyncDelayGuard] = args[0]  # type: ignore[assignment]
+            this: WalletCommons[SyncRemoteBeekeeper[InterfaceSettings], SyncWalletLocked, SyncDelayGuard] = args[0]  # type: ignore[assignment]
             this._sync_call_callback_if_locked(wallet_name=this.name, token=this.session_token)
             return wrapped_function(*args, **kwrags)  # type: ignore[return-value]
 
diff --git a/beekeepy/beekeepy/_remote_handle/__init__.py b/beekeepy/beekeepy/_remote_handle/__init__.py
index e69de29b..e8d89f4d 100644
--- a/beekeepy/beekeepy/_remote_handle/__init__.py
+++ b/beekeepy/beekeepy/_remote_handle/__init__.py
@@ -0,0 +1,30 @@
+from __future__ import annotations
+
+from beekeepy._remote_handle.abc.batch_handle import ApiFactory, AsyncBatchHandle, SyncBatchHandle
+from beekeepy._remote_handle.abc.handle import AbstractAsyncHandle, AbstractSyncHandle
+from beekeepy._remote_handle.app_status_probe import AppStatusProbe
+from beekeepy._remote_handle.beekeeper import AsyncBeekeeper as AsyncBeekeeperTemplate
+from beekeepy._remote_handle.beekeeper import Beekeeper as BeekeeperTemplate
+from beekeepy._remote_handle.beekeeper import _AsyncSessionBatchHandle as AsyncBeekeeprBatchHandle
+from beekeepy._remote_handle.beekeeper import _SyncSessionBatchHandle as SyncBeekeeprBatchHandle
+from beekeepy._remote_handle.settings import RemoteHandleSettings
+
+AsyncBeekeeper = AsyncBeekeeperTemplate[RemoteHandleSettings]
+Beekeeper = BeekeeperTemplate[RemoteHandleSettings]
+
+__all__ = [
+    "AbstractAsyncHandle",
+    "AbstractSyncHandle",
+    "ApiFactory",
+    "AppStatusProbe",
+    "AsyncBatchHandle",
+    "AsyncBeekeeper",
+    "AsyncBeekeeperTemplate",
+    "AsyncBeekeeprBatchHandle",
+    "Beekeeper",
+    "BeekeeperTemplate",
+    "RemoteHandleSettings",
+    "SyncBatchHandle",
+    "SyncBatchHandle",
+    "SyncBeekeeprBatchHandle",
+]
diff --git a/beekeepy/beekeepy/_remote_handle/abc/batch_handle.py b/beekeepy/beekeepy/_remote_handle/abc/batch_handle.py
index a8e364f4..e0ad80a1 100644
--- a/beekeepy/beekeepy/_remote_handle/abc/batch_handle.py
+++ b/beekeepy/beekeepy/_remote_handle/abc/batch_handle.py
@@ -6,8 +6,9 @@ from dataclasses import dataclass
 from typing import TYPE_CHECKING, Any, Generic, TypeVar
 
 from beekeepy import exceptions
-from beekeepy._interface.context import ContextAsync, ContextSync, EnterReturnT
-from beekeepy._remote_handle.build_json_rpc_call import build_json_rpc_call
+from beekeepy._apis.abc import AsyncSendable, SyncSendable
+from beekeepy._utilities.build_json_rpc_call import build_json_rpc_call
+from beekeepy._utilities.context import ContextAsync, ContextSync, EnterReturnT
 from schemas.jsonrpc import ExpectResultT, JSONRPCResult, get_response_model
 
 if TYPE_CHECKING:
@@ -15,8 +16,7 @@ if TYPE_CHECKING:
 
     from typing_extensions import Self
 
-    from beekeepy._communication.abc.overseer import AbstractOverseer
-    from beekeepy._interface.url import HttpUrl
+    from beekeepy._communication import AbstractOverseer, HttpUrl
 
 
 class _DelayedResponseWrapper:
@@ -207,7 +207,7 @@ OwnerT = TypeVar("OwnerT")
 ApiFactory = Callable[[OwnerT], ApiT]
 
 
-class SyncBatchHandle(_BatchHandle["SyncBatchHandle"], Generic[ApiT]):  # type: ignore[type-arg]
+class SyncBatchHandle(_BatchHandle["SyncBatchHandle"], SyncSendable, Generic[ApiT]):  # type: ignore[type-arg]
     def __init__(
         self,
         url: HttpUrl,
@@ -224,7 +224,7 @@ class SyncBatchHandle(_BatchHandle["SyncBatchHandle"], Generic[ApiT]):  # type:
         return self._impl_handle_request(endpoint, params, expect_type=expected_type)  # type: ignore[arg-type]
 
 
-class AsyncBatchHandle(_BatchHandle["AsyncBatchHandle"], Generic[ApiT]):  # type: ignore[type-arg]
+class AsyncBatchHandle(_BatchHandle["AsyncBatchHandle"], AsyncSendable, Generic[ApiT]):  # type: ignore[type-arg]
     def __init__(
         self,
         url: HttpUrl,
diff --git a/beekeepy/beekeepy/_remote_handle/abc/handle.py b/beekeepy/beekeepy/_remote_handle/abc/handle.py
index dc77ea88..3005e4f1 100644
--- a/beekeepy/beekeepy/_remote_handle/abc/handle.py
+++ b/beekeepy/beekeepy/_remote_handle/abc/handle.py
@@ -5,36 +5,40 @@ from typing import TYPE_CHECKING, Any, Generic, TypeVar
 
 from loguru import logger as loguru_logger
 
-from beekeepy._communication.aiohttp_communicator import AioHttpCommunicator
-from beekeepy._communication.request_communicator import RequestCommunicator
-from beekeepy._interface.context import SelfContextAsync, SelfContextSync
-from beekeepy._interface.settings_holder import UniqueSettingsHolder
-from beekeepy._interface.stopwatch import Stopwatch
-from beekeepy._remote_handle.build_json_rpc_call import build_json_rpc_call
-from beekeepy._remote_handle.settings import Settings
+from beekeepy._apis.abc import (
+    AbstractAsyncApiCollection,
+    AbstractSyncApiCollection,
+)
+from beekeepy._apis.abc.sendable import AsyncSendable, SyncSendable
+from beekeepy._communication import AioHttpCommunicator, RequestCommunicator
+from beekeepy._remote_handle.settings import RemoteHandleSettings
+from beekeepy._utilities.build_json_rpc_call import build_json_rpc_call
+from beekeepy._utilities.context import SelfContextAsync, SelfContextSync
+from beekeepy._utilities.settings_holder import UniqueSettingsHolder
+from beekeepy._utilities.stopwatch import Stopwatch
 from beekeepy.exceptions import CommunicationError
 from schemas.jsonrpc import ExpectResultT, JSONRPCResult, get_response_model
 
 if TYPE_CHECKING:
     from loguru import Logger
 
-    from beekeepy._communication.abc.communicator import AbstractCommunicator
-    from beekeepy._communication.abc.overseer import AbstractOverseer
-    from beekeepy._interface.url import HttpUrl
-    from beekeepy._remote_handle.batch_handle import AsyncBatchHandle, SyncBatchHandle
+    from beekeepy._communication import AbstractCommunicator, AbstractOverseer, HttpUrl
+    from beekeepy._remote_handle.abc.batch_handle import AsyncBatchHandle, SyncBatchHandle
     from beekeepy.exceptions import Json
 
+RemoteSettingsT = TypeVar("RemoteSettingsT", bound=RemoteHandleSettings)
 
-ApiT = TypeVar("ApiT")
 
+ApiT = TypeVar("ApiT", bound=AbstractAsyncApiCollection | AbstractSyncApiCollection)
 
-class AbstractHandle(UniqueSettingsHolder[Settings], ABC, Generic[ApiT]):
+
+class AbstractHandle(UniqueSettingsHolder[RemoteSettingsT], ABC, Generic[RemoteSettingsT, ApiT]):
     """Provides basic interface for all network handles."""
 
     def __init__(
         self,
         *args: Any,
-        settings: Settings,
+        settings: RemoteSettingsT,
         logger: Logger | None = None,
         **kwargs: Any,
     ) -> None:
@@ -63,6 +67,10 @@ class AbstractHandle(UniqueSettingsHolder[Settings], ABC, Generic[ApiT]):
         with self.update_settings() as settings:
             settings.http_endpoint = value
 
+    @property
+    def apis(self) -> ApiT:
+        return self.__api
+
     @property
     def api(self) -> ApiT:
         return self.__api
@@ -138,14 +146,14 @@ class AbstractHandle(UniqueSettingsHolder[Settings], ABC, Generic[ApiT]):
         )
 
 
-class AbstractAsyncHandle(AbstractHandle[ApiT], SelfContextAsync, ABC):
+class AbstractAsyncHandle(AbstractHandle[RemoteSettingsT, ApiT], SelfContextAsync, AsyncSendable, ABC):
     """Base class for service handlers that uses asynchronous communication."""
 
     async def _async_send(
         self, *, endpoint: str, params: str, expected_type: type[ExpectResultT]
     ) -> JSONRPCResult[ExpectResultT]:
         """Sends data asynchronously to handled service basing on jsonrpc."""
-        from beekeepy._interface.error_logger import ErrorLogger
+        from beekeepy._utilities.error_logger import ErrorLogger
 
         request = build_json_rpc_call(method=endpoint, params=params)
         self._log_request(request)
@@ -168,12 +176,12 @@ class AbstractAsyncHandle(AbstractHandle[ApiT], SelfContextAsync, ABC):
         self.teardown()
 
 
-class AbstractSyncHandle(AbstractHandle[ApiT], SelfContextSync, ABC):
+class AbstractSyncHandle(AbstractHandle[RemoteSettingsT, ApiT], SelfContextSync, SyncSendable, ABC):
     """Base class for service handlers that uses synchronous communication."""
 
     def _send(self, *, endpoint: str, params: str, expected_type: type[ExpectResultT]) -> JSONRPCResult[ExpectResultT]:
         """Sends data synchronously to handled service basing on jsonrpc."""
-        from beekeepy._interface.error_logger import ErrorLogger
+        from beekeepy._utilities.error_logger import ErrorLogger
 
         request = build_json_rpc_call(method=endpoint, params=params)
         self._log_request(request)
diff --git a/beekeepy/beekeepy/_remote_handle/beekeeper.py b/beekeepy/beekeepy/_remote_handle/beekeeper.py
index cd21bb10..eac3915d 100644
--- a/beekeepy/beekeepy/_remote_handle/beekeeper.py
+++ b/beekeepy/beekeepy/_remote_handle/beekeeper.py
@@ -3,21 +3,24 @@ from __future__ import annotations
 import uuid
 from typing import TYPE_CHECKING, Any, NoReturn
 
-from beekeepy._interface._sanitize import sanitize
-from beekeepy._remote_handle.abc.handle import AbstractAsyncHandle, AbstractSyncHandle
-from beekeepy._remote_handle.api.api_collection import (
+from beekeepy._apis import (
+    AsyncBeekeeperApi,
     BeekeeperAsyncApiCollection,
     BeekeeperSyncApiCollection,
+    SyncBeekeeperApi,
 )
-from beekeepy._remote_handle.api.session_holder import AsyncSessionHolder, SyncSessionHolder
-from beekeepy._remote_handle.batch_handle import ApiFactory, AsyncBatchHandle, SyncBatchHandle
+from beekeepy._apis.abc import (
+    AsyncSessionHolder,
+    SyncSessionHolder,
+)
+from beekeepy._remote_handle.abc.batch_handle import ApiFactory, AsyncBatchHandle, SyncBatchHandle
+from beekeepy._remote_handle.abc.handle import AbstractAsyncHandle, AbstractSyncHandle, RemoteSettingsT
+from beekeepy._utilities.sanitize import sanitize
 
 if TYPE_CHECKING:
     from typing_extensions import Self
 
-    from beekeepy._communication.abc.overseer import AbstractOverseer
-    from beekeepy._interface.url import HttpUrl
-    from beekeepy._remote_handle.api import AsyncBeekeeperApi, SyncBeekeeperApi
+    from beekeepy._communication import AbstractOverseer, HttpUrl
     from beekeepy.exceptions import Json
 
 _handle_target_service_name = "beekeeper"
@@ -67,15 +70,19 @@ class _AsyncSessionBatchHandle(AsyncBatchHandle[BeekeeperAsyncApiCollection], As
         _raise_acquire_not_possible()
 
 
-class Beekeeper(AbstractSyncHandle[BeekeeperSyncApiCollection], SyncSessionHolder):
+class Beekeeper(AbstractSyncHandle[RemoteSettingsT, BeekeeperSyncApiCollection], SyncSessionHolder):
     """Synchronous handle for beekeeper service communication."""
 
     def _construct_api(self) -> BeekeeperSyncApiCollection:
         return BeekeeperSyncApiCollection(owner=self)
 
+    @property
+    def apis(self) -> BeekeeperSyncApiCollection:
+        return super().api
+
     @property
     def api(self) -> SyncBeekeeperApi:  # type: ignore[override]
-        return super().api.beekeeper
+        return self.apis.beekeeper
 
     def _target_service(self) -> str:
         return _handle_target_service_name
@@ -99,15 +106,19 @@ class Beekeeper(AbstractSyncHandle[BeekeeperSyncApiCollection], SyncSessionHolde
         return sanitize(data)
 
 
-class AsyncBeekeeper(AbstractAsyncHandle[BeekeeperAsyncApiCollection], AsyncSessionHolder):
+class AsyncBeekeeper(AbstractAsyncHandle[RemoteSettingsT, BeekeeperAsyncApiCollection], AsyncSessionHolder):
     """Asynchronous handle for beekeeper service communication."""
 
     def _construct_api(self) -> BeekeeperAsyncApiCollection:
         return BeekeeperAsyncApiCollection(owner=self)
 
+    @property
+    def apis(self) -> BeekeeperAsyncApiCollection:
+        return super().api
+
     @property
     def api(self) -> AsyncBeekeeperApi:  # type: ignore[override]
-        return super().api.beekeeper
+        return self.apis.beekeeper
 
     def _target_service(self) -> str:
         return _handle_target_service_name
diff --git a/beekeepy/beekeepy/_runnable_handle/__init__.py b/beekeepy/beekeepy/_runnable_handle/__init__.py
index 3e4206d5..65e1c9a6 100644
--- a/beekeepy/beekeepy/_runnable_handle/__init__.py
+++ b/beekeepy/beekeepy/_runnable_handle/__init__.py
@@ -1,6 +1,21 @@
 from __future__ import annotations
 
-from beekeepy._runnable_handle.beekeeper import AsyncBeekeeper, Beekeeper
-from beekeepy._runnable_handle.notification_handler_base import BeekeeperNotificationHandler
+from beekeepy._runnable_handle.beekeeper import AsyncBeekeeper as AsyncBeekeeperTemplate
+from beekeepy._runnable_handle.beekeeper import Beekeeper as BeekeeperTemplate
+from beekeepy._runnable_handle.callbacks_protocol import AsyncWalletLocked, SyncWalletLocked
+from beekeepy._runnable_handle.close_already_running_beekeeper import close_already_running_beekeeper
+from beekeepy._runnable_handle.settings import Settings as RunnableHandleSettings
 
-__all__ = ["AsyncBeekeeper", "Beekeeper", "BeekeeperNotificationHandler"]
+AsyncBeekeeper = AsyncBeekeeperTemplate[RunnableHandleSettings]
+Beekeeper = BeekeeperTemplate[RunnableHandleSettings]
+
+__all__ = [
+    "AsyncBeekeeper",
+    "AsyncBeekeeperTemplate",
+    "AsyncWalletLocked",
+    "Beekeeper",
+    "BeekeeperTemplate",
+    "close_already_running_beekeeper",
+    "RunnableHandleSettings",
+    "SyncWalletLocked",
+]
diff --git a/beekeepy/beekeepy/_runnable_handle/settings.py b/beekeepy/beekeepy/_runnable_handle/settings.py
index 7adf312c..1ee8b0c2 100644
--- a/beekeepy/beekeepy/_runnable_handle/settings.py
+++ b/beekeepy/beekeepy/_runnable_handle/settings.py
@@ -5,9 +5,8 @@ from distutils.util import strtobool
 from pathlib import Path
 from typing import ClassVar
 
-from beekeepy._communication.abc.communicator import AbstractCommunicator  # noqa: TCH001
-from beekeepy._interface.url import HttpUrl  # noqa: TCH001
-from beekeepy._remote_handle.settings import Settings as RemoteHandleSettings
+from beekeepy._communication import HttpUrl  # noqa: TCH001
+from beekeepy._remote_handle import RemoteHandleSettings
 
 
 class Settings(RemoteHandleSettings):
diff --git a/beekeepy/beekeepy/exceptions/base.py b/beekeepy/beekeepy/exceptions/base.py
index d59b0d84..ba088f05 100644
--- a/beekeepy/beekeepy/exceptions/base.py
+++ b/beekeepy/beekeepy/exceptions/base.py
@@ -5,18 +5,18 @@ from typing import TYPE_CHECKING, Any
 
 from pydantic import StrRegexError
 
-from beekeepy._interface.context import ContextSync
+from beekeepy._utilities.context import ContextSync
 
 if TYPE_CHECKING:
     from types import TracebackType
 
-    from beekeepy._interface.url import Url
+    from beekeepy._communication import Url
 
 Json = dict[str, Any]
 CommunicationResponseT = str | Json | list[Json]
 
 
-class BeekeepyError(Exception):
+class BeekeepyError(BaseException, ABC):
     """Base class for all exception raised by beekeepy."""
 
     @property
diff --git a/beekeepy/beekeepy/exceptions/common.py b/beekeepy/beekeepy/exceptions/common.py
index 29b3efd0..93bc622e 100644
--- a/beekeepy/beekeepy/exceptions/common.py
+++ b/beekeepy/beekeepy/exceptions/common.py
@@ -13,7 +13,7 @@ from beekeepy.exceptions.base import (
 )
 
 if TYPE_CHECKING:
-    from beekeepy._interface.url import Url
+    from beekeepy._communication import Url
 
 
 class BatchRequestError(BeekeepyError):
@@ -59,14 +59,6 @@ class WalletIsLockedError(BeekeepyError):
         super().__init__(f"Wallet `{wallet_name}` is locked.")
 
 
-class TimeoutReachWhileCloseError(BeekeepyError):
-    """Raises when beekeeper did not closed during specified timeout."""
-
-    def __init__(self) -> None:
-        """Constructor."""
-        super().__init__("Process was force-closed with SIGKILL, because didn't close before timeout")
-
-
 class NotPositiveTimeError(BeekeepyError):
     """Raises when given time value was 0 or negative."""
 
diff --git a/beekeepy/beekeepy/exceptions/overseer.py b/beekeepy/beekeepy/exceptions/overseer.py
index 1fdc616d..4007e825 100644
--- a/beekeepy/beekeepy/exceptions/overseer.py
+++ b/beekeepy/beekeepy/exceptions/overseer.py
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Any, Sequence
 from beekeepy.exceptions.base import BeekeepyError, CommunicationResponseT, Json, OverseerError
 
 if TYPE_CHECKING:
-    from beekeepy._interface.url import Url
+    from beekeepy._communication import Url
 
 
 class UnableToAcquireDatabaseLockError(OverseerError):
@@ -110,7 +110,7 @@ class ErrorInResponseError(OverseerError):
 
 
 class GroupedErrorsError(BeekeepyError):
-    def __init__(self, exceptions: Sequence[Exception]) -> None:
+    def __init__(self, exceptions: Sequence[BaseException]) -> None:
         self.exceptions = list(exceptions)
 
     def get_exception_for(self, *, request_id: int) -> OverseerError | None:
diff --git a/beekeepy/beekeepy/handle/remote.py b/beekeepy/beekeepy/handle/remote.py
index e0a6bea4..e5330f99 100644
--- a/beekeepy/beekeepy/handle/remote.py
+++ b/beekeepy/beekeepy/handle/remote.py
@@ -1,11 +1,21 @@
 from __future__ import annotations
 
-from beekeepy._remote_handle.abc.api import AbstractAsyncApi, AbstractSyncApi, AsyncHandleT
-from beekeepy._remote_handle.abc.api_collection import AbstractAsyncApiCollection, AbstractSyncApiCollection
-from beekeepy._remote_handle.abc.handle import AbstractAsyncHandle, AbstractSyncHandle
-from beekeepy._remote_handle.batch_handle import AsyncBatchHandle, SyncBatchHandle
-from beekeepy._remote_handle.beekeeper import AsyncBeekeeper, Beekeeper
-from beekeepy._remote_handle.settings import Settings as RemoteSettings
+from beekeepy._apis.abc import (
+    AbstractAsyncApi,
+    AbstractAsyncApiCollection,
+    AbstractSyncApi,
+    AbstractSyncApiCollection,
+    RegisteredApisT,
+)
+from beekeepy._remote_handle import (
+    AbstractAsyncHandle,
+    AbstractSyncHandle,
+    AsyncBatchHandle,
+    AsyncBeekeeper,
+    Beekeeper,
+    RemoteHandleSettings,
+    SyncBatchHandle,
+)
 
 __all__ = [
     "AbstractAsyncApi",
@@ -16,8 +26,8 @@ __all__ = [
     "AbstractSyncHandle",
     "AsyncBatchHandle",
     "AsyncBeekeeper",
-    "AsyncHandleT",
     "Beekeeper",
-    "RemoteSettings",
+    "RegisteredApisT",
+    "RemoteHandleSettings",
     "SyncBatchHandle",
 ]
diff --git a/beekeepy/beekeepy/handle/runnable.py b/beekeepy/beekeepy/handle/runnable.py
index 0b2b567c..247ed9e6 100644
--- a/beekeepy/beekeepy/handle/runnable.py
+++ b/beekeepy/beekeepy/handle/runnable.py
@@ -1,27 +1,14 @@
 from __future__ import annotations
 
-from beekeepy._communication.appbase_notification_handler import AppbaseNotificationHandler
-from beekeepy._communication.async_server import AsyncHttpServer
-from beekeepy._communication.notification_decorator import notification
-from beekeepy._communication.universal_notification_server import UniversalNotificationHandler
-from beekeepy._executable.arguments.beekeeper_arguments import BeekeeperArguments, BeekeeperArgumentsDefaults
-from beekeepy._executable.beekeeper_config import BeekeeperConfig
-from beekeepy._runnable_handle.beekeeper import AsyncBeekeeper, Beekeeper
-from beekeepy._runnable_handle.close_already_running_beekeeper import close_already_running_beekeeper
-from beekeepy._runnable_handle.notification_handler_base import BeekeeperNotificationHandler
-from beekeepy._runnable_handle.settings import Settings as RunnableSettings
+from beekeepy._executable import BeekeeperArguments, BeekeeperConfig, BeekeeperExecutable
+from beekeepy._runnable_handle import AsyncBeekeeper, Beekeeper, RunnableHandleSettings, close_already_running_beekeeper
 
 __all__ = [
-    "AppbaseNotificationHandler",
     "AsyncBeekeeper",
-    "AsyncHttpServer",
     "Beekeeper",
-    "BeekeeperArguments",
-    "BeekeeperArgumentsDefaults",
     "BeekeeperConfig",
-    "BeekeeperNotificationHandler",
+    "BeekeeperArguments",
+    "BeekeeperExecutable",
     "close_already_running_beekeeper",
-    "notification",
-    "RunnableSettings",
-    "UniversalNotificationHandler",
+    "RunnableHandleSettings",
 ]
diff --git a/beekeepy/beekeepy/interfaces.py b/beekeepy/beekeepy/interfaces.py
index 65742fb8..4b137461 100644
--- a/beekeepy/beekeepy/interfaces.py
+++ b/beekeepy/beekeepy/interfaces.py
@@ -1,27 +1,22 @@
 from __future__ import annotations
 
-from beekeepy._interface._sanitize import mask, sanitize
-from beekeepy._interface._suppress_api_not_found import SuppressApiNotFound
-from beekeepy._interface.context import (
-    ContextAsync,
-    ContextSync,
-    SelfContextAsync,
-    SelfContextSync,
-)
-from beekeepy._interface.context_settings_updater import ContextSettingsUpdater
-from beekeepy._interface.error_logger import ErrorLogger
-from beekeepy._interface.key_pair import KeyPair
-from beekeepy._interface.settings_holder import (
-    SharedSettingsHolder,
-    UniqueSettingsHolder,
-)
-from beekeepy._interface.stopwatch import Stopwatch, StopwatchResult
-from beekeepy._interface.url import HttpUrl, P2PUrl, Url, WsUrl
+from beekeepy._communication import HttpUrl, P2PUrl, Url, WsUrl
+from beekeepy._utilities.context import ContextAsync, ContextSync, SelfContextAsync, SelfContextSync
+from beekeepy._utilities.context_settings_updater import ContextSettingsUpdater
+from beekeepy._utilities.delay_guard import AsyncDelayGuard, DelayGuardBase, SyncDelayGuard
+from beekeepy._utilities.error_logger import ErrorLogger
+from beekeepy._utilities.key_pair import KeyPair
+from beekeepy._utilities.sanitize import mask, sanitize
+from beekeepy._utilities.settings_holder import SharedSettingsHolder, UniqueSettingsHolder
+from beekeepy._utilities.stopwatch import Stopwatch, StopwatchResult
+from beekeepy._utilities.suppress_api_not_found import SuppressApiNotFound
 
 __all__ = [
+    "AsyncDelayGuard",
     "ContextAsync",
     "ContextSettingsUpdater",
     "ContextSync",
+    "DelayGuardBase",
     "ErrorLogger",
     "HttpUrl",
     "KeyPair",
@@ -34,6 +29,7 @@ __all__ = [
     "Stopwatch",
     "StopwatchResult",
     "SuppressApiNotFound",
+    "SyncDelayGuard",
     "UniqueSettingsHolder",
     "Url",
     "WsUrl",
diff --git a/tests/beekeepy_test/handle/commandline/application_command_line_options/patterns/generate_help_pattern.py b/tests/beekeepy_test/handle/commandline/application_command_line_options/patterns/generate_help_pattern.py
index 67224e33..c59a3fa3 100644
--- a/tests/beekeepy_test/handle/commandline/application_command_line_options/patterns/generate_help_pattern.py
+++ b/tests/beekeepy_test/handle/commandline/application_command_line_options/patterns/generate_help_pattern.py
@@ -5,9 +5,12 @@ from pathlib import Path
 import loguru
 
 from beekeepy import Settings
-from beekeepy._executable.beekeeper_executable import BeekeeperExecutable
+from beekeepy.handle.runnable import BeekeeperExecutable
 
 if __name__ == "__main__":
-    help_text = BeekeeperExecutable(settings=Settings(), logger=loguru.logger).get_help_text()
+    settings = Settings()
+    help_text = BeekeeperExecutable(
+        executable_path=settings.binary_path, working_directory=settings.ensured_working_directory, logger=loguru.logger
+    ).get_help_text()
     help_pattern_file = Path.cwd() / "help_pattern.txt"
     help_pattern_file.write_text(help_text.strip())
diff --git a/tests/beekeepy_test/handle/commandline/application_options/test_backtrace.py b/tests/beekeepy_test/handle/commandline/application_options/test_backtrace.py
index b843da39..8310070f 100644
--- a/tests/beekeepy_test/handle/commandline/application_options/test_backtrace.py
+++ b/tests/beekeepy_test/handle/commandline/application_options/test_backtrace.py
@@ -6,10 +6,10 @@ from typing import TYPE_CHECKING, Literal
 import pytest
 from local_tools.beekeepy import checkers
 
-from beekeepy._executable.arguments.beekeeper_arguments import BeekeeperArguments
+from beekeepy.handle.runnable import BeekeeperArguments
 
 if TYPE_CHECKING:
-    from beekeepy._executable.beekeeper_executable import (
+    from beekeepy.handle.runnable import (
         BeekeeperExecutable,
     )
 
@@ -19,9 +19,10 @@ def test_backtrace(backtrace: Literal["yes", "no"], beekeeper_exe: BeekeeperExec
     """Test will check command line flag --backtrace."""
     # ARRAGNE & ACT
 
-    with beekeeper_exe.run(
+    with beekeeper_exe.restore_arguments(
+        BeekeeperArguments(data_dir=beekeeper_exe.working_directory, backtrace=backtrace)
+    ), beekeeper_exe.run(
         blocking=False,
-        arguments=BeekeeperArguments(data_dir=beekeeper_exe.working_directory, backtrace=backtrace),
     ):
         time.sleep(0.1)
         # ASSERT
diff --git a/tests/beekeepy_test/handle/commandline/application_options/test_export_keys_wallet.py b/tests/beekeepy_test/handle/commandline/application_options/test_export_keys_wallet.py
index b2cb2f02..5f5a1104 100644
--- a/tests/beekeepy_test/handle/commandline/application_options/test_export_keys_wallet.py
+++ b/tests/beekeepy_test/handle/commandline/application_options/test_export_keys_wallet.py
@@ -3,8 +3,8 @@ from __future__ import annotations
 import json
 from typing import TYPE_CHECKING, Final
 
-from beekeepy._interface.key_pair import KeyPair
 from beekeepy.handle.runnable import Beekeeper
+from beekeepy.interfaces import KeyPair
 
 if TYPE_CHECKING:
     from pathlib import Path
diff --git a/tests/beekeepy_test/handle/commandline/application_options/test_log_json_rpc.py b/tests/beekeepy_test/handle/commandline/application_options/test_log_json_rpc.py
index af22146a..1e48dbd8 100644
--- a/tests/beekeepy_test/handle/commandline/application_options/test_log_json_rpc.py
+++ b/tests/beekeepy_test/handle/commandline/application_options/test_log_json_rpc.py
@@ -2,7 +2,7 @@ from __future__ import annotations
 
 from typing import TYPE_CHECKING
 
-from beekeepy._executable.arguments.beekeeper_arguments import BeekeeperArguments
+from beekeepy.handle.runnable import BeekeeperArguments
 
 if TYPE_CHECKING:
     from pathlib import Path
diff --git a/tests/beekeepy_test/handle/commandline/application_options/test_unlock_timeout.py b/tests/beekeepy_test/handle/commandline/application_options/test_unlock_timeout.py
index b04bb005..a04ff79b 100644
--- a/tests/beekeepy_test/handle/commandline/application_options/test_unlock_timeout.py
+++ b/tests/beekeepy_test/handle/commandline/application_options/test_unlock_timeout.py
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
 
 import pytest
 
-from beekeepy._executable.arguments.beekeeper_arguments import BeekeeperArguments
+from beekeepy.handle.runnable import BeekeeperArguments
 
 if TYPE_CHECKING:
     from beekeepy.handle.runnable import Beekeeper
diff --git a/tests/beekeepy_test/handle/commandline/application_options/test_wallet_dir.py b/tests/beekeepy_test/handle/commandline/application_options/test_wallet_dir.py
index ec879fec..0a967be3 100644
--- a/tests/beekeepy_test/handle/commandline/application_options/test_wallet_dir.py
+++ b/tests/beekeepy_test/handle/commandline/application_options/test_wallet_dir.py
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
 import pytest
 
 if TYPE_CHECKING:
-    from beekeepy._remote_handle.beekeeper import Beekeeper
+    from beekeepy.handle.remote import Beekeeper
 
 
 def check_wallets_size(beekeeper: Beekeeper, required_size: int) -> None:
diff --git a/tests/beekeepy_test/handle/commandline/application_options/test_webserver_http_endpoint.py b/tests/beekeepy_test/handle/commandline/application_options/test_webserver_http_endpoint.py
index 97793b53..ae56ed72 100644
--- a/tests/beekeepy_test/handle/commandline/application_options/test_webserver_http_endpoint.py
+++ b/tests/beekeepy_test/handle/commandline/application_options/test_webserver_http_endpoint.py
@@ -7,8 +7,8 @@ import pytest
 import requests
 from local_tools.beekeepy.network import get_port
 
-from beekeepy._executable.arguments.beekeeper_arguments import BeekeeperArguments
-from beekeepy._interface.url import HttpUrl
+from beekeepy.handle.runnable import BeekeeperArguments
+from beekeepy.interfaces import HttpUrl
 from schemas.apis import beekeeper_api
 from schemas.jsonrpc import get_response_model
 
diff --git a/tests/beekeepy_test/handle/commandline/application_options/test_webserver_thread_pool_size.py b/tests/beekeepy_test/handle/commandline/application_options/test_webserver_thread_pool_size.py
index 38dbfb82..0f042945 100644
--- a/tests/beekeepy_test/handle/commandline/application_options/test_webserver_thread_pool_size.py
+++ b/tests/beekeepy_test/handle/commandline/application_options/test_webserver_thread_pool_size.py
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
 import pytest
 from local_tools.beekeepy import checkers
 
-from beekeepy._executable.arguments.beekeeper_arguments import BeekeeperArguments
+from beekeepy.handle.runnable import BeekeeperArguments
 
 if TYPE_CHECKING:
     from beekeepy.handle.runnable import Beekeeper
diff --git a/tests/beekeepy_test/handle/commandline/conftest.py b/tests/beekeepy_test/handle/commandline/conftest.py
index 2b34069b..bea91cb9 100644
--- a/tests/beekeepy_test/handle/commandline/conftest.py
+++ b/tests/beekeepy_test/handle/commandline/conftest.py
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING
 
 import pytest
 
-from beekeepy._executable.beekeeper_executable import BeekeeperExecutable
+from beekeepy.handle.runnable import BeekeeperExecutable
 
 if TYPE_CHECKING:
     from local_tools.beekeepy.models import SettingsLoggerFactory
@@ -13,4 +13,8 @@ if TYPE_CHECKING:
 @pytest.fixture
 def beekeeper_exe(settings_with_logger: SettingsLoggerFactory) -> BeekeeperExecutable:
     incoming_settings, logger = settings_with_logger()
-    return BeekeeperExecutable(settings=incoming_settings, logger=logger)
+    return BeekeeperExecutable(
+        executable_path=incoming_settings.binary_path,
+        working_directory=incoming_settings.ensured_working_directory,
+        logger=logger,
+    )
diff --git a/tests/beekeepy_test/handle/storage/test_storage.py b/tests/beekeepy_test/handle/storage/test_storage.py
index e98e83a7..7fd9420f 100644
--- a/tests/beekeepy_test/handle/storage/test_storage.py
+++ b/tests/beekeepy_test/handle/storage/test_storage.py
@@ -1,17 +1,16 @@
 from __future__ import annotations
 
-import json
 import shutil
-from pathlib import Path
+from typing import TYPE_CHECKING
 
-import pytest
 from local_tools.beekeepy import checkers
 from loguru import logger
 
 from beekeepy import Settings
-from beekeepy.exceptions import BeekeeperFailedToStartError
 from beekeepy.handle.runnable import Beekeeper
-from beekeepy.interfaces import HttpUrl
+
+if TYPE_CHECKING:
+    from pathlib import Path
 
 
 def prepare_directory(path: Path) -> None:
diff --git a/tests/beekeepy_test/handle/various/test_blocking_unlock.py b/tests/beekeepy_test/handle/various/test_blocking_unlock.py
index 821630bb..70310523 100644
--- a/tests/beekeepy_test/handle/various/test_blocking_unlock.py
+++ b/tests/beekeepy_test/handle/various/test_blocking_unlock.py
@@ -8,12 +8,12 @@ from local_tools.beekeepy.generators import default_wallet_credentials
 from local_tools.beekeepy.models import SettingsLoggerFactory, WalletInfo
 from local_tools.beekeepy.network import async_raw_http_call
 
-from beekeepy._interface.delay_guard import DelayGuardBase
 from beekeepy.handle.runnable import AsyncBeekeeper
+from beekeepy.interfaces import DelayGuardBase
 from schemas.jsonrpc import JSONRPCRequest
 
 if TYPE_CHECKING:
-    from beekeepy._interface.url import HttpUrl as Url
+    from beekeepy.interfaces import HttpUrl as Url
 
 
 # We have 500ms time period protection on ulocking wallet.
diff --git a/tests/beekeepy_test/interface/test_setup.py b/tests/beekeepy_test/interface/test_setup.py
index 9680c4cb..a8607533 100644
--- a/tests/beekeepy_test/interface/test_setup.py
+++ b/tests/beekeepy_test/interface/test_setup.py
@@ -2,7 +2,10 @@ from __future__ import annotations
 
 from typing import TYPE_CHECKING
 
+import pytest
+
 from beekeepy import Beekeeper
+from beekeepy.exceptions import InvalidatedStateByClosingBeekeeperError, InvalidatedStateByClosingSessionError
 
 if TYPE_CHECKING:
     from local_tools.beekeepy.models import SettingsFactory
@@ -10,19 +13,20 @@ if TYPE_CHECKING:
 
 def test_closing_with_delete(settings: SettingsFactory) -> None:
     # ARRANGE
-    sets = settings()
-    bk = Beekeeper.factory(settings=sets)
+    bk = Beekeeper.factory(settings=settings())
 
     # ACT & ASSERT (no throw)
     bk.teardown()
-    assert not (sets.ensured_working_directory / "beekeeper.pid").exists()
+    with pytest.raises(InvalidatedStateByClosingBeekeeperError):
+        bk.create_session()
 
 
 def test_closing_with_with(settings: SettingsFactory) -> None:
     # ARRANGE, ACT & ASSERT (no throw)
-    sets = settings()
-    with Beekeeper.factory(settings=sets):
-        assert (sets.ensured_working_directory / "beekeeper.pid").exists()
+    with Beekeeper.factory(settings=settings()) as bk, bk.create_session() as session:
+        pass
+    with pytest.raises(InvalidatedStateByClosingSessionError):
+        session.wallets  # noqa: B018  # part of test
 
 
 def test_session_tokens(settings: SettingsFactory) -> None:
diff --git a/tests/beekeepy_test/interface/test_standalone_beekeeper.py b/tests/beekeepy_test/interface/test_standalone_beekeeper.py
index 47f5833d..265cc1eb 100644
--- a/tests/beekeepy_test/interface/test_standalone_beekeeper.py
+++ b/tests/beekeepy_test/interface/test_standalone_beekeeper.py
@@ -1,6 +1,5 @@
 from __future__ import annotations
 
-import json
 import os
 import sys
 import time
@@ -30,8 +29,7 @@ def verify_beekeeper_status(path_or_pid: Path | int, alive: bool) -> int:
     pid: int | None = None
     if isinstance(path_or_pid, Path):
         assert path_or_pid.exists(), f"Beekeeper started too slow, missing file: {path_or_pid.as_posix()}"
-        with path_or_pid.open("r") as rfile:
-            pid = int(json.load(rfile).get("pid", -1))
+        pid = int(path_or_pid.read_text().strip())
     else:
         pid = path_or_pid
 
@@ -45,7 +43,7 @@ def test_standalone_beekeeper(working_directory: Path) -> None:
     path_to_resource_directory = Path(__file__).resolve().parent / "resources"
     path_to_script = path_to_resource_directory / "standalone_beekeeper_by_args.py"
     path_to_working_directory = working_directory / "wdir"
-    path_to_pid_file = path_to_working_directory / "beekeeper.pid"
+    path_to_pid_file = path_to_working_directory / "pid.txt"
 
     # ACT & ASSERT
     run_python_script(
diff --git a/tests/conftest.py b/tests/conftest.py
index 8e438830..d2441bd5 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -6,7 +6,7 @@ from pathlib import Path
 
 import pytest
 
-from beekeepy._remote_handle.abc.api import AbstractApi, RegisteredApisT
+from beekeepy.handle.remote import AbstractSyncApi, RegisteredApisT
 from beekeepy.interfaces import HttpUrl
 
 
diff --git a/tests/local-tools/local_tools/beekeepy/account_credentials.py b/tests/local-tools/local_tools/beekeepy/account_credentials.py
index a30be4bf..c52da96a 100644
--- a/tests/local-tools/local_tools/beekeepy/account_credentials.py
+++ b/tests/local-tools/local_tools/beekeepy/account_credentials.py
@@ -3,7 +3,7 @@ from __future__ import annotations
 import random
 from typing import Final
 
-from beekeepy._interface.key_pair import KeyPair
+from beekeepy.interfaces import KeyPair
 
 ACCOUNTS_DATA: Final[list[dict[str, str]]] = [
     {
diff --git a/tests/local-tools/local_tools/beekeepy/network.py b/tests/local-tools/local_tools/beekeepy/network.py
index 2daa290b..40ff64ce 100644
--- a/tests/local-tools/local_tools/beekeepy/network.py
+++ b/tests/local-tools/local_tools/beekeepy/network.py
@@ -4,18 +4,17 @@ import socket
 from json import loads
 from typing import TYPE_CHECKING, Any
 
-from beekeepy import Settings
-from beekeepy._communication.aiohttp_communicator import AioHttpCommunicator
-from beekeepy._communication.request_communicator import RequestCommunicator
+from beekeepy._communication import AioHttpCommunicator, RequestCommunicator
+from beekeepy.handle.remote import RemoteHandleSettings
 
 if TYPE_CHECKING:
-    from beekeepy._interface.url import HttpUrl
+    from beekeepy.interfaces import HttpUrl
     from schemas.jsonrpc import JSONRPCRequest
 
 
 async def async_raw_http_call(*, http_endpoint: HttpUrl, data: JSONRPCRequest) -> dict[str, Any]:
     """Make raw call with given data to given http_endpoint."""
-    communicator = AioHttpCommunicator(settings=Settings(http_endpoint=http_endpoint))
+    communicator = AioHttpCommunicator(settings=RemoteHandleSettings(http_endpoint=http_endpoint))
     response = await communicator.async_send(url=http_endpoint, data=data.json(by_alias=True))
     parsed = loads(response)
     assert isinstance(parsed, dict), "expected json object"
@@ -24,7 +23,7 @@ async def async_raw_http_call(*, http_endpoint: HttpUrl, data: JSONRPCRequest) -
 
 def raw_http_call(*, http_endpoint: HttpUrl, data: JSONRPCRequest) -> dict[str, Any]:
     """Make raw call with given data to given http_endpoint."""
-    communicator = RequestCommunicator(settings=Settings(http_endpoint=http_endpoint))
+    communicator = RequestCommunicator(settings=RemoteHandleSettings(http_endpoint=http_endpoint))
     response = communicator.send(url=http_endpoint, data=data.json(by_alias=True))
     parsed = loads(response)
     assert isinstance(parsed, dict), "expected json object"
-- 
GitLab


From 3366efd820003dfbeb616221826f6b080a82def4 Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Sat, 22 Mar 2025 20:20:27 +0000
Subject: [PATCH 09/23] Add beekeeper_arguments

---
 .../_executable/beekeeper_arguments.py        | 43 +++++++++++++++++++
 1 file changed, 43 insertions(+)
 create mode 100644 beekeepy/beekeepy/_executable/beekeeper_arguments.py

diff --git a/beekeepy/beekeepy/_executable/beekeeper_arguments.py b/beekeepy/beekeepy/_executable/beekeeper_arguments.py
new file mode 100644
index 00000000..5af2501b
--- /dev/null
+++ b/beekeepy/beekeepy/_executable/beekeeper_arguments.py
@@ -0,0 +1,43 @@
+from __future__ import annotations
+
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Any, ClassVar, Literal
+
+from beekeepy._communication import HttpUrl
+from beekeepy._executable.abc.arguments import Arguments
+
+
+class BeekeeperArgumentsDefaults:
+    DEFAULT_BACKTRACE: ClassVar[Literal["yes", "no"]] = "yes"
+    DEFAULT_EXPORT_KEYS_WALLET: ClassVar[ExportKeysWalletParams | None] = None
+    DEFAULT_LOG_JSON_RPC: ClassVar[Path | None] = None
+    DEFAULT_UNLOCK_TIMEOUT: ClassVar[int] = 900
+    DEFAULT_UNLOCK_INTERVAL: ClassVar[int] = 500
+    DEFAULT_WALLET_DIR: ClassVar[Path] = Path.cwd()
+    DEFAULT_WEBSERVER_THREAD_POOL_SIZE: ClassVar[int] = 32
+    DEFAULT_WEBSERVER_HTTP_ENDPOINT: ClassVar[HttpUrl | None] = None
+
+
+@dataclass
+class ExportKeysWalletParams:
+    wallet_name: str
+    wallet_password: str
+
+
+class BeekeeperArguments(Arguments):
+    backtrace: Literal["yes", "no"] | None = BeekeeperArgumentsDefaults.DEFAULT_BACKTRACE
+    data_dir: Path | None = None
+    export_keys_wallet: ExportKeysWalletParams | None = BeekeeperArgumentsDefaults.DEFAULT_EXPORT_KEYS_WALLET
+    log_json_rpc: Path | None = BeekeeperArgumentsDefaults.DEFAULT_LOG_JSON_RPC
+    unlock_timeout: int | None = BeekeeperArgumentsDefaults.DEFAULT_UNLOCK_TIMEOUT
+    wallet_dir: Path | None = BeekeeperArgumentsDefaults.DEFAULT_WALLET_DIR
+    webserver_thread_pool_size: int | None = BeekeeperArgumentsDefaults.DEFAULT_WEBSERVER_THREAD_POOL_SIZE
+    webserver_http_endpoint: HttpUrl | None = BeekeeperArgumentsDefaults.DEFAULT_WEBSERVER_HTTP_ENDPOINT
+
+    def _convert_member_value_to_string_default(self, member_value: Any) -> str | Any:
+        if isinstance(member_value, HttpUrl):
+            return member_value.as_string(with_protocol=False)
+        if isinstance(member_value, ExportKeysWalletParams):
+            return f'["{member_value.wallet_name}","{member_value.wallet_password}"]'
+        return member_value
-- 
GitLab


From 4a0d85a3b37f78042f14beabc20b5643176d3e2c Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Sat, 22 Mar 2025 20:23:26 +0000
Subject: [PATCH 10/23] Add InterfaceSettings to easly distinguish Settings
 types

---
 beekeepy/beekeepy/_interface/__init__.py      |  2 ++
 .../_interface/abc/asynchronous/beekeeper.py  | 10 +++---
 .../_interface/abc/synchronous/beekeeper.py   | 10 +++---
 .../_interface/asynchronous/beekeeper.py      | 34 +++++++++++-------
 .../_interface/asynchronous/session.py        |  6 ++--
 .../_interface/asynchronous/wallet.py         |  9 ++---
 beekeepy/beekeepy/_interface/settings.py      |  7 ++++
 .../_interface/synchronous/beekeeper.py       | 36 +++++++++++--------
 .../_interface/synchronous/session.py         |  6 ++--
 .../beekeepy/_interface/synchronous/wallet.py |  7 ++--
 10 files changed, 77 insertions(+), 50 deletions(-)
 create mode 100644 beekeepy/beekeepy/_interface/settings.py

diff --git a/beekeepy/beekeepy/_interface/__init__.py b/beekeepy/beekeepy/_interface/__init__.py
index 9802b2ef..1a826ee5 100644
--- a/beekeepy/beekeepy/_interface/__init__.py
+++ b/beekeepy/beekeepy/_interface/__init__.py
@@ -5,6 +5,7 @@ from beekeepy._interface.asynchronous.beekeeper import Beekeeper as AsyncBeekepe
 from beekeepy._interface.asynchronous.session import Session as AsyncSession
 from beekeepy._interface.asynchronous.wallet import UnlockedWallet as AsyncUnlockedWallet
 from beekeepy._interface.asynchronous.wallet import Wallet as AsyncWallet
+from beekeepy._interface.settings import InterfaceSettings
 from beekeepy._interface.synchronous.beekeeper import Beekeeper
 from beekeepy._interface.synchronous.session import Session
 from beekeepy._interface.synchronous.wallet import UnlockedWallet, Wallet
@@ -16,6 +17,7 @@ __all__ = [
     "AsyncUnlockedWallet",
     "AsyncWallet",
     "Beekeeper",
+    "InterfaceSettings",
     "Session",
     "UnlockedWallet",
     "Wallet",
diff --git a/beekeepy/beekeepy/_interface/abc/asynchronous/beekeeper.py b/beekeepy/beekeepy/_interface/abc/asynchronous/beekeeper.py
index ddda2365..0604b7bc 100644
--- a/beekeepy/beekeepy/_interface/abc/asynchronous/beekeeper.py
+++ b/beekeepy/beekeepy/_interface/abc/asynchronous/beekeeper.py
@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
 from typing import TYPE_CHECKING, cast
 
 from beekeepy._communication import CommunicationSettings
-from beekeepy._interface.context import ContextAsync
+from beekeepy._interface.settings import InterfaceSettings
 from beekeepy._utilities.context import ContextAsync
 from beekeepy._utilities.context_settings_updater import ContextSettingsUpdater
 
@@ -19,9 +19,9 @@ class Beekeeper(ContextAsync["Beekeeper"], ContextSettingsUpdater[CommunicationS
     async def create_session(self, *, salt: str | None = None) -> Session: ...
 
     @property
-    def settings(self) -> Settings:
+    def settings(self) -> InterfaceSettings:
         """Returns read-only settings."""
-        return cast(Settings, self._get_copy_of_settings())
+        return cast(InterfaceSettings, self._get_copy_of_settings())
 
     @property
     def http_endpoint(self) -> HttpUrl:
@@ -42,13 +42,13 @@ class Beekeeper(ContextAsync["Beekeeper"], ContextSettingsUpdater[CommunicationS
         """Detaches process and returns PID."""
 
     @classmethod
-    async def factory(cls, *, settings: Settings | None = None) -> Beekeeper:
+    async def factory(cls, *, settings: InterfaceSettings | None = None) -> Beekeeper:
         from beekeepy._interface.asynchronous.beekeeper import Beekeeper as BeekeeperImplementation
 
         return await BeekeeperImplementation._factory(settings=settings)
 
     @classmethod
-    async def remote_factory(cls, *, url_or_settings: Settings | HttpUrl) -> Beekeeper:
+    async def remote_factory(cls, *, url_or_settings: InterfaceSettings | HttpUrl) -> Beekeeper:
         from beekeepy._interface.asynchronous.beekeeper import Beekeeper as BeekeeperImplementation
 
         return await BeekeeperImplementation._remote_factory(url_or_settings=url_or_settings)
diff --git a/beekeepy/beekeepy/_interface/abc/synchronous/beekeeper.py b/beekeepy/beekeepy/_interface/abc/synchronous/beekeeper.py
index ac6dc8ed..a8068fb2 100644
--- a/beekeepy/beekeepy/_interface/abc/synchronous/beekeeper.py
+++ b/beekeepy/beekeepy/_interface/abc/synchronous/beekeeper.py
@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
 from typing import TYPE_CHECKING, cast
 
 from beekeepy._communication import CommunicationSettings
-from beekeepy._interface.context import ContextSync
+from beekeepy._interface.settings import InterfaceSettings
 from beekeepy._utilities.context import ContextSync
 from beekeepy._utilities.context_settings_updater import ContextSettingsUpdater
 
@@ -19,9 +19,9 @@ class Beekeeper(ContextSync["Beekeeper"], ContextSettingsUpdater[CommunicationSe
     def create_session(self, *, salt: str | None = None) -> Session: ...
 
     @property
-    def settings(self) -> Settings:
+    def settings(self) -> InterfaceSettings:
         """Returns read-only settings."""
-        return cast(Settings, self._get_copy_of_settings())
+        return cast(InterfaceSettings, self._get_copy_of_settings())
 
     @property
     def http_endpoint(self) -> HttpUrl:
@@ -42,13 +42,13 @@ class Beekeeper(ContextSync["Beekeeper"], ContextSettingsUpdater[CommunicationSe
         """Detaches process and returns PID."""
 
     @classmethod
-    def factory(cls, *, settings: Settings | None = None) -> Beekeeper:
+    def factory(cls, *, settings: InterfaceSettings | None = None) -> Beekeeper:
         from beekeepy._interface.synchronous.beekeeper import Beekeeper as BeekeeperImplementation
 
         return BeekeeperImplementation._factory(settings=settings)
 
     @classmethod
-    def remote_factory(cls, *, url_or_settings: Settings | HttpUrl) -> Beekeeper:
+    def remote_factory(cls, *, url_or_settings: InterfaceSettings | HttpUrl) -> Beekeeper:
         from beekeepy._interface.synchronous.beekeeper import Beekeeper as BeekeeperImplementation
 
         return BeekeeperImplementation._remote_factory(url_or_settings=url_or_settings)
diff --git a/beekeepy/beekeepy/_interface/asynchronous/beekeeper.py b/beekeepy/beekeepy/_interface/asynchronous/beekeeper.py
index b54837bb..d195568d 100644
--- a/beekeepy/beekeepy/_interface/asynchronous/beekeeper.py
+++ b/beekeepy/beekeepy/_interface/asynchronous/beekeeper.py
@@ -7,9 +7,9 @@ from loguru import logger
 from beekeepy._interface.abc.asynchronous.beekeeper import Beekeeper as BeekeeperInterface
 from beekeepy._interface.abc.packed_object import PackedAsyncBeekeeper
 from beekeepy._interface.asynchronous.session import Session
-from beekeepy._interface.delay_guard import AsyncDelayGuard
-from beekeepy._interface.state_invalidator import StateInvalidator
-from beekeepy._remote_handle.beekeeper import AsyncBeekeeper as AsynchronousRemoteBeekeeperHandle
+from beekeepy._interface.settings import InterfaceSettings
+from beekeepy._remote_handle import AsyncBeekeeperTemplate as AsynchronousRemoteBeekeeperHandle
+from beekeepy._runnable_handle import AsyncBeekeeperTemplate as AsynchronousBeekeeperHandle
 from beekeepy._utilities.delay_guard import AsyncDelayGuard
 from beekeepy._utilities.state_invalidator import StateInvalidator
 from beekeepy.exceptions import (
@@ -26,7 +26,7 @@ if TYPE_CHECKING:
 
 
 class Beekeeper(BeekeeperInterface, StateInvalidator):
-    def __init__(self, *args: Any, handle: AsynchronousRemoteBeekeeperHandle, **kwargs: Any) -> None:
+    def __init__(self, *args: Any, handle: AsynchronousRemoteBeekeeperHandle[InterfaceSettings], **kwargs: Any) -> None:
         super().__init__(*args, **kwargs)
         self.__instance = handle
         self.__guard = AsyncDelayGuard()
@@ -47,7 +47,7 @@ class Beekeeper(BeekeeperInterface, StateInvalidator):
             self.__default_session = self.__create_session((await self._get_instance().session).token)
         return self.__default_session
 
-    def _get_instance(self) -> AsynchronousRemoteBeekeeperHandle:
+    def _get_instance(self) -> AsynchronousRemoteBeekeeperHandle[InterfaceSettings]:
         return self.__instance
 
     @StateInvalidator.empty_call_after_invalidation(None)
@@ -79,28 +79,38 @@ class Beekeeper(BeekeeperInterface, StateInvalidator):
         return PackedAsyncBeekeeper(settings=self.settings, unpack_factory=Beekeeper._remote_factory)
 
     @classmethod
-    async def _factory(cls, *, settings: Settings | None = None) -> BeekeeperInterface:
-        settings = settings or Settings()
-        handle = AsynchronousBeekeeperHandle(settings=settings, logger=logger)
+    async def _factory(cls, *, settings: InterfaceSettings | None = None) -> BeekeeperInterface:
+        settings = settings or InterfaceSettings()
+        handle = cls.__create_local_handle(settings=settings)
         handle.run()
         return cls(handle=handle)
 
     @classmethod
-    async def _remote_factory(cls, *, url_or_settings: Settings | HttpUrl) -> BeekeeperInterface:
-        if isinstance(url_or_settings, Settings):
+    async def _remote_factory(cls, *, url_or_settings: InterfaceSettings | HttpUrl) -> BeekeeperInterface:
+        if isinstance(url_or_settings, InterfaceSettings):
             assert (
                 url_or_settings.http_endpoint is not None
             ), "Settings.http_endpoint has to be set when passing to remote_factory"
-        settings = url_or_settings if isinstance(url_or_settings, Settings) else Settings(http_endpoint=url_or_settings)
+        settings = (
+            url_or_settings
+            if isinstance(url_or_settings, InterfaceSettings)
+            else InterfaceSettings(http_endpoint=url_or_settings)
+        )
         handle = AsynchronousRemoteBeekeeperHandle(settings=settings)
         cls.__apply_existing_session_token(settings=settings, handle=handle)
         return cls(handle=handle)
 
     @classmethod
-    def __apply_existing_session_token(cls, settings: Settings, handle: AsynchronousRemoteBeekeeperHandle) -> None:
+    def __apply_existing_session_token(
+        cls, settings: InterfaceSettings, handle: AsynchronousRemoteBeekeeperHandle[InterfaceSettings]
+    ) -> None:
         if settings.use_existing_session:
             handle.set_session_token(settings.use_existing_session)
 
+    @classmethod
+    def __create_local_handle(cls, settings: InterfaceSettings) -> AsynchronousBeekeeperHandle[InterfaceSettings]:
+        return AsynchronousBeekeeperHandle(settings=settings, logger=logger)
+
     async def _aenter(self) -> BeekeeperInterface:
         return self
 
diff --git a/beekeepy/beekeepy/_interface/asynchronous/session.py b/beekeepy/beekeepy/_interface/asynchronous/session.py
index 35e3704e..470f58d0 100644
--- a/beekeepy/beekeepy/_interface/asynchronous/session.py
+++ b/beekeepy/beekeepy/_interface/asynchronous/session.py
@@ -27,8 +27,8 @@ if TYPE_CHECKING:
     from beekeepy._interface.abc.asynchronous.wallet import (
         Wallet as WalletInterface,
     )
-    from beekeepy._interface.delay_guard import AsyncDelayGuard
-    from beekeepy._remote_handle.beekeeper import AsyncBeekeeper as AsynchronousRemoteBeekeeperHandle
+    from beekeepy._interface.settings import InterfaceSettings
+    from beekeepy._remote_handle import AsyncBeekeeperTemplate as AsynchronousRemoteBeekeeperHandle
     from beekeepy._utilities.delay_guard import AsyncDelayGuard
     from schemas.apis.beekeeper_api import GetInfo
     from schemas.fields.basic import PublicKey
@@ -39,7 +39,7 @@ class Session(SessionInterface, StateInvalidator):
     def __init__(
         self,
         *args: Any,
-        beekeeper: AsynchronousRemoteBeekeeperHandle,
+        beekeeper: AsynchronousRemoteBeekeeperHandle[InterfaceSettings],
         guard: AsyncDelayGuard,
         use_session_token: str | None = None,
         default_session_close_callback: Callable[[], None] | None = None,
diff --git a/beekeepy/beekeepy/_interface/asynchronous/wallet.py b/beekeepy/beekeepy/_interface/asynchronous/wallet.py
index 5cfac6dc..20173c12 100644
--- a/beekeepy/beekeepy/_interface/asynchronous/wallet.py
+++ b/beekeepy/beekeepy/_interface/asynchronous/wallet.py
@@ -8,11 +8,10 @@ from beekeepy._interface.abc.asynchronous.wallet import (
 from beekeepy._interface.abc.asynchronous.wallet import (
     Wallet as WalletInterface,
 )
-from beekeepy._interface.delay_guard import AsyncDelayGuard
+from beekeepy._interface.settings import InterfaceSettings
 from beekeepy._interface.validators import validate_digest, validate_private_keys, validate_public_keys
 from beekeepy._interface.wallets_common import WalletCommons
-from beekeepy._remote_handle.beekeeper import AsyncBeekeeper as AsyncRemoteBeekeeper
-from beekeepy._runnable_handle.callbacks_protocol import AsyncWalletLocked
+from beekeepy._remote_handle import AsyncBeekeeperTemplate as AsyncRemoteBeekeeper
 from beekeepy._runnable_handle import AsyncWalletLocked
 from beekeepy._utilities.delay_guard import AsyncDelayGuard
 from beekeepy.exceptions import (
@@ -31,7 +30,9 @@ if TYPE_CHECKING:
     from schemas.fields.hex import Signature
 
 
-class Wallet(WalletCommons[AsyncRemoteBeekeeper, AsyncWalletLocked, AsyncDelayGuard], WalletInterface):
+class Wallet(
+    WalletCommons[AsyncRemoteBeekeeper[InterfaceSettings], AsyncWalletLocked, AsyncDelayGuard], WalletInterface
+):
     @property
     async def public_keys(self) -> list[PublicKey]:
         return [
diff --git a/beekeepy/beekeepy/_interface/settings.py b/beekeepy/beekeepy/_interface/settings.py
new file mode 100644
index 00000000..701919db
--- /dev/null
+++ b/beekeepy/beekeepy/_interface/settings.py
@@ -0,0 +1,7 @@
+from __future__ import annotations
+
+from beekeepy._runnable_handle import RunnableHandleSettings
+
+
+class InterfaceSettings(RunnableHandleSettings):
+    """Settings for beekeeper interface."""
diff --git a/beekeepy/beekeepy/_interface/synchronous/beekeeper.py b/beekeepy/beekeepy/_interface/synchronous/beekeeper.py
index d6042276..e287b00b 100644
--- a/beekeepy/beekeepy/_interface/synchronous/beekeeper.py
+++ b/beekeepy/beekeepy/_interface/synchronous/beekeeper.py
@@ -6,12 +6,10 @@ from loguru import logger
 
 from beekeepy._interface.abc.packed_object import PackedSyncBeekeeper
 from beekeepy._interface.abc.synchronous.beekeeper import Beekeeper as BeekeeperInterface
-from beekeepy._interface.delay_guard import SyncDelayGuard
-from beekeepy._interface.state_invalidator import StateInvalidator
+from beekeepy._interface.settings import InterfaceSettings
 from beekeepy._interface.synchronous.session import Session
-from beekeepy._remote_handle.beekeeper import Beekeeper as SynchronousRemoteBeekeeperHandle
-from beekeepy._runnable_handle.beekeeper import Beekeeper as SynchronousBeekeeperHandle
-from beekeepy._runnable_handle.settings import Settings
+from beekeepy._remote_handle import BeekeeperTemplate as SynchronousRemoteBeekeeperHandle
+from beekeepy._runnable_handle import BeekeeperTemplate as SynchronousBeekeeperHandle
 from beekeepy._utilities.delay_guard import SyncDelayGuard
 from beekeepy._utilities.state_invalidator import StateInvalidator
 from beekeepy.exceptions import (
@@ -28,7 +26,7 @@ if TYPE_CHECKING:
 
 
 class Beekeeper(BeekeeperInterface, StateInvalidator):
-    def __init__(self, *args: Any, handle: SynchronousRemoteBeekeeperHandle, **kwargs: Any) -> None:
+    def __init__(self, *args: Any, handle: SynchronousRemoteBeekeeperHandle[InterfaceSettings], **kwargs: Any) -> None:
         super().__init__(*args, **kwargs)
         self.__instance = handle
         self.__guard = SyncDelayGuard()
@@ -49,7 +47,7 @@ class Beekeeper(BeekeeperInterface, StateInvalidator):
             self.__default_session = self.__create_session(self._get_instance().session.token, default_session=True)
         return self.__default_session
 
-    def _get_instance(self) -> SynchronousRemoteBeekeeperHandle:
+    def _get_instance(self) -> SynchronousRemoteBeekeeperHandle[InterfaceSettings]:
         return self.__instance
 
     @StateInvalidator.empty_call_after_invalidation(None)
@@ -81,28 +79,38 @@ class Beekeeper(BeekeeperInterface, StateInvalidator):
         return PackedSyncBeekeeper(settings=self.settings, unpack_factory=Beekeeper._remote_factory)
 
     @classmethod
-    def _factory(cls, *, settings: Settings | None = None) -> BeekeeperInterface:
-        settings = settings or Settings()
-        handle = SynchronousBeekeeperHandle(settings=settings, logger=logger)
+    def _factory(cls, *, settings: InterfaceSettings | None = None) -> BeekeeperInterface:
+        settings = settings or InterfaceSettings()
+        handle = cls.__create_local_handle(settings=settings)
         handle.run()
         return cls(handle=handle)
 
     @classmethod
-    def _remote_factory(cls, *, url_or_settings: Settings | HttpUrl) -> BeekeeperInterface:
-        if isinstance(url_or_settings, Settings):
+    def _remote_factory(cls, *, url_or_settings: InterfaceSettings | HttpUrl) -> BeekeeperInterface:
+        if isinstance(url_or_settings, InterfaceSettings):
             assert (
                 url_or_settings.http_endpoint is not None
             ), "Settings.http_endpoint has to be set when passing to remote_factory"
-        settings = url_or_settings if isinstance(url_or_settings, Settings) else Settings(http_endpoint=url_or_settings)
+        settings = (
+            url_or_settings
+            if isinstance(url_or_settings, InterfaceSettings)
+            else InterfaceSettings(http_endpoint=url_or_settings)
+        )
         handle = SynchronousRemoteBeekeeperHandle(settings=settings)
         cls.__apply_existing_session_token(settings=settings, handle=handle)
         return cls(handle=handle)
 
     @classmethod
-    def __apply_existing_session_token(cls, settings: Settings, handle: SynchronousRemoteBeekeeperHandle) -> None:
+    def __apply_existing_session_token(
+        cls, settings: InterfaceSettings, handle: SynchronousRemoteBeekeeperHandle[InterfaceSettings]
+    ) -> None:
         if settings.use_existing_session:
             handle.set_session_token(settings.use_existing_session)
 
+    @classmethod
+    def __create_local_handle(cls, settings: InterfaceSettings) -> SynchronousBeekeeperHandle[InterfaceSettings]:
+        return SynchronousBeekeeperHandle(settings=settings, logger=logger)
+
     def _enter(self) -> BeekeeperInterface:
         return self
 
diff --git a/beekeepy/beekeepy/_interface/synchronous/session.py b/beekeepy/beekeepy/_interface/synchronous/session.py
index 9fab6de8..5b9e559d 100644
--- a/beekeepy/beekeepy/_interface/synchronous/session.py
+++ b/beekeepy/beekeepy/_interface/synchronous/session.py
@@ -26,8 +26,8 @@ if TYPE_CHECKING:
     from beekeepy._interface.abc.synchronous.wallet import (
         Wallet as WalletInterface,
     )
-    from beekeepy._interface.delay_guard import SyncDelayGuard
-    from beekeepy._remote_handle.beekeeper import Beekeeper as SyncRemoteBeekeeper
+    from beekeepy._interface.settings import InterfaceSettings
+    from beekeepy._remote_handle import BeekeeperTemplate as SyncRemoteBeekeeper
     from beekeepy._utilities.delay_guard import SyncDelayGuard
     from schemas.apis.beekeeper_api import GetInfo
     from schemas.fields.basic import PublicKey
@@ -38,7 +38,7 @@ class Session(SessionInterface, StateInvalidator):
     def __init__(
         self,
         *args: Any,
-        beekeeper: SyncRemoteBeekeeper,
+        beekeeper: SyncRemoteBeekeeper[InterfaceSettings],
         guard: SyncDelayGuard,
         use_session_token: str | None = None,
         default_session_close_callback: Callable[[], None] | None = None,
diff --git a/beekeepy/beekeepy/_interface/synchronous/wallet.py b/beekeepy/beekeepy/_interface/synchronous/wallet.py
index 5fbbb202..81ffec89 100644
--- a/beekeepy/beekeepy/_interface/synchronous/wallet.py
+++ b/beekeepy/beekeepy/_interface/synchronous/wallet.py
@@ -8,11 +8,10 @@ from beekeepy._interface.abc.synchronous.wallet import (
 from beekeepy._interface.abc.synchronous.wallet import (
     Wallet as WalletInterface,
 )
-from beekeepy._interface.delay_guard import SyncDelayGuard
+from beekeepy._interface.settings import InterfaceSettings
 from beekeepy._interface.validators import validate_private_keys, validate_public_keys
 from beekeepy._interface.wallets_common import WalletCommons
-from beekeepy._remote_handle.beekeeper import Beekeeper as SyncRemoteBeekeeper
-from beekeepy._runnable_handle.callbacks_protocol import SyncWalletLocked
+from beekeepy._remote_handle import BeekeeperTemplate as SyncRemoteBeekeeper
 from beekeepy._runnable_handle import SyncWalletLocked
 from beekeepy._utilities.delay_guard import SyncDelayGuard
 from beekeepy.exceptions import (
@@ -31,7 +30,7 @@ if TYPE_CHECKING:
     from schemas.fields.hex import Signature
 
 
-class Wallet(WalletCommons[SyncRemoteBeekeeper, SyncWalletLocked, SyncDelayGuard], WalletInterface):
+class Wallet(WalletCommons[SyncRemoteBeekeeper[InterfaceSettings], SyncWalletLocked, SyncDelayGuard], WalletInterface):
     @property
     def public_keys(self) -> list[PublicKey]:
         return [
-- 
GitLab


From 4ceeab8c477149737c34add729b7b30e7f783e12 Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Sat, 22 Mar 2025 20:23:58 +0000
Subject: [PATCH 11/23] Fill missing imports in _executable sub-package
 interface

---
 beekeepy/beekeepy/_executable/__init__.py | 24 +++++++++++++++++++----
 1 file changed, 20 insertions(+), 4 deletions(-)

diff --git a/beekeepy/beekeepy/_executable/__init__.py b/beekeepy/beekeepy/_executable/__init__.py
index a734003e..6b444e5d 100644
--- a/beekeepy/beekeepy/_executable/__init__.py
+++ b/beekeepy/beekeepy/_executable/__init__.py
@@ -1,13 +1,29 @@
 from __future__ import annotations
 
-from beekeepy._executable.arguments.arguments import Arguments
-from beekeepy._executable.arguments.beekeeper_arguments import BeekeeperArguments
+from beekeepy._executable.abc import (
+    Arguments,
+    ArgumentT,
+    Config,
+    ConfigT,
+    Executable,
+    StreamRepresentation,
+    StreamsHolder,
+)
+from beekeepy._executable.beekeeper_arguments import BeekeeperArguments
+from beekeepy._executable.beekeeper_config import BeekeeperConfig
 from beekeepy._executable.beekeeper_executable import BeekeeperExecutable
-from beekeepy._executable.executable import Executable
+from beekeepy._utilities.key_pair import KeyPair
 
 __all__ = [
-    "Arguments",
+    "BeekeeperConfig",
     "BeekeeperArguments",
     "BeekeeperExecutable",
+    "Arguments",
     "Executable",
+    "Config",
+    "StreamRepresentation",
+    "StreamsHolder",
+    "ArgumentT",
+    "ConfigT",
+    "KeyPair",
 ]
-- 
GitLab


From b0445fa2f4f318335e150d509699cee41b12c40e Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Sat, 22 Mar 2025 20:35:57 +0000
Subject: [PATCH 12/23] Update way of generating generated_ test directories

---
 .../patterns/config.ini                          |  2 +-
 tests/conftest.py                                | 16 +++++++++-------
 2 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/tests/beekeepy_test/handle/commandline/application_command_line_options/patterns/config.ini b/tests/beekeepy_test/handle/commandline/application_command_line_options/patterns/config.ini
index 1b428e92..f55ef484 100644
--- a/tests/beekeepy_test/handle/commandline/application_command_line_options/patterns/config.ini
+++ b/tests/beekeepy_test/handle/commandline/application_command_line_options/patterns/config.ini
@@ -1,5 +1,5 @@
 # config automatically generated by helpy
-wallet-dir=/workspace/hive/tests/python/functional/beekeepy/handle/commandline/application_command_line_options/patterns
+wallet-dir=/workspace/helpy/tests/beekeepy_test/handle/commandline/application_command_line_options/patterns
 unlock-timeout=900
 unlock-interval=500
 webserver-http-endpoint=0.0.0.0:0
diff --git a/tests/conftest.py b/tests/conftest.py
index d2441bd5..b505bdcb 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -30,12 +30,14 @@ def _convert_test_name_to_directory_name(test_name: str) -> str:
 @pytest.fixture(autouse=True)
 def working_directory(request: pytest.FixtureRequest) -> Path:
     name_of_directory = _convert_test_name_to_directory_name(request.node.name)
-    path_to_generated = request.node.path.parent / name_of_directory
-    if path_to_generated.exists():
-        shutil.rmtree(path_to_generated)
-    path_to_generated.mkdir()
-    assert isinstance(path_to_generated, Path), "given object is not Path"
-    return path_to_generated
+    path_to_module_generated = request.node.path.parent / f"generated_{request.node.path.stem}"
+    path_to_module_generated.mkdir(exist_ok=True)
+    path_to_test_artifacts = path_to_module_generated / name_of_directory
+    if path_to_test_artifacts.exists():
+        shutil.rmtree(path_to_test_artifacts)
+    path_to_test_artifacts.mkdir()
+    assert isinstance(path_to_test_artifacts, Path), "given object is not Path"
+    return path_to_test_artifacts
 
 
 def pytest_addoption(parser: pytest.Parser) -> None:
@@ -47,7 +49,7 @@ def pytest_addoption(parser: pytest.Parser) -> None:
 @pytest.fixture
 def registered_apis() -> RegisteredApisT:
     """Return registered methods."""
-    return AbstractApi._get_registered_methods()
+    return AbstractSyncApi._get_registered_methods()
 
 
 @pytest.fixture
-- 
GitLab


From 1986fb73a755eee6552e452d4ac21db00c99f56b Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Sat, 22 Mar 2025 20:37:22 +0000
Subject: [PATCH 13/23] Fix in test due to change of is_running from property
 to method

---
 .../application_options/test_export_keys_wallet.py          | 2 +-
 tests/beekeepy_test/handle/conftest.py                      | 2 +-
 tests/beekeepy_test/handle/storage/test_storage.py          | 6 +++---
 tests/beekeepy_test/handle/various/test_blocking_unlock.py  | 2 +-
 4 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/tests/beekeepy_test/handle/commandline/application_options/test_export_keys_wallet.py b/tests/beekeepy_test/handle/commandline/application_options/test_export_keys_wallet.py
index 5f5a1104..b609ec5c 100644
--- a/tests/beekeepy_test/handle/commandline/application_options/test_export_keys_wallet.py
+++ b/tests/beekeepy_test/handle/commandline/application_options/test_export_keys_wallet.py
@@ -91,4 +91,4 @@ def test_export_keys(beekeeper: Beekeeper) -> None:
     # ASSERT
     # Check default path of wallet_name.keys
     check_dumped_keys(extract_path / wallet_name_keys, keys1)
-    assert bk.is_running is False
+    assert bk.is_running() is False
diff --git a/tests/beekeepy_test/handle/conftest.py b/tests/beekeepy_test/handle/conftest.py
index d13ae688..d5512de6 100644
--- a/tests/beekeepy_test/handle/conftest.py
+++ b/tests/beekeepy_test/handle/conftest.py
@@ -27,7 +27,7 @@ def beekeeper_not_started(settings_with_logger: SettingsLoggerFactory) -> Iterat
 
     yield bk
 
-    if bk.is_running:
+    if bk.is_running():
         bk.teardown()
 
 
diff --git a/tests/beekeepy_test/handle/storage/test_storage.py b/tests/beekeepy_test/handle/storage/test_storage.py
index 7fd9420f..bdf54514 100644
--- a/tests/beekeepy_test/handle/storage/test_storage.py
+++ b/tests/beekeepy_test/handle/storage/test_storage.py
@@ -31,7 +31,7 @@ def test_multiply_beekeepeer_same_storage(working_directory: Path) -> None:
 
     # ACT & ASSERT 1
     with Beekeeper(settings=settings, logger=logger) as bk1:
-        assert bk1.is_running is True, "First instance of beekeeper should launch without any problems."
+        assert bk1.is_running() is True, "First instance of beekeeper should launch without any problems."
 
         # ACT & ASSERT 2
         bk2 = Beekeeper(settings=settings, logger=logger)
@@ -62,8 +62,8 @@ def test_multiply_beekeepeer_different_storage(working_directory: Path) -> None:
         settings=Settings(working_directory=bk2_path), logger=logger
     ) as bk2:
         # ASSERT
-        assert bk1.is_running, "First instance of beekeeper should be working."
-        assert bk2.is_running, "Second instance of beekeeper should be working."
+        assert bk1.is_running(), "First instance of beekeeper should be working."
+        assert bk2.is_running(), "Second instance of beekeeper should be working."
         bks.extend((bk1, bk2))
 
     for bk in bks:
diff --git a/tests/beekeepy_test/handle/various/test_blocking_unlock.py b/tests/beekeepy_test/handle/various/test_blocking_unlock.py
index 70310523..485d6bcd 100644
--- a/tests/beekeepy_test/handle/various/test_blocking_unlock.py
+++ b/tests/beekeepy_test/handle/various/test_blocking_unlock.py
@@ -29,7 +29,7 @@ def beekeeper_not_started(settings_with_logger: SettingsLoggerFactory) -> Iterat
 
     yield bk
 
-    if bk.is_running:
+    if bk.is_running():
         bk.teardown()
 
 
-- 
GitLab


From e4561c4dfe82e49a68155152c3b07eeddba3fd77 Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Sat, 22 Mar 2025 20:39:25 +0000
Subject: [PATCH 14/23] Fix tests because of changes in beekeeper behaviour

list_wallets -> list_created_wallets change: list_wallets does not refresh timeout and tests aware on this will fail
---
 .../handle/api_tests/test_api_close.py        |  8 +++-
 .../handle/api_tests/test_api_set_timeout.py  |  4 +-
 .../beekeepy_test/handle/basic/test_wallet.py |  4 +-
 .../patterns/help_pattern.txt                 |  6 +--
 .../test_unlock_timeout.py                    |  2 +-
 .../handle/storage/test_storage.py            | 44 ++-----------------
 6 files changed, 16 insertions(+), 52 deletions(-)

diff --git a/tests/beekeepy_test/handle/api_tests/test_api_close.py b/tests/beekeepy_test/handle/api_tests/test_api_close.py
index e4c56b92..dc521f72 100644
--- a/tests/beekeepy_test/handle/api_tests/test_api_close.py
+++ b/tests/beekeepy_test/handle/api_tests/test_api_close.py
@@ -14,6 +14,10 @@ if TYPE_CHECKING:
     from beekeepy.handle.runnable import Beekeeper
 
 
+# NOTE 1: Beekeeper should not raise exception while calling close on already closed wallet or not existing wallet.
+#         It should be treated as a success for UX purposes.
+
+
 def test_api_close(beekeeper: Beekeeper, wallet: WalletInfo) -> None:
     """Test test_api_close will test beekeeper_api.close api call."""
     # ARRANGE
@@ -51,7 +55,7 @@ def test_api_close_double_close(
     beekeeper.api.close(wallet_name=wallet.name)
 
     # ASSERT
-    # According to behavior change of Beekeeper, it should not throw
+    # SHOULD NOT RAISE (NOTE 1)
     beekeeper.api.close(wallet_name=wallet.name)
 
 
@@ -61,5 +65,5 @@ def test_api_close_not_existing_wallet(beekeeper: Beekeeper) -> None:
     wallet = WalletInfo(password=generate_wallet_password(), name=generate_wallet_name())
 
     # ACT & ASSERT
-    # According to behavior change of Beekeeper, it should not throw
+    # SHOULD NOT RAISE (NOTE 1)
     beekeeper.api.close(wallet_name=wallet.name)
diff --git a/tests/beekeepy_test/handle/api_tests/test_api_set_timeout.py b/tests/beekeepy_test/handle/api_tests/test_api_set_timeout.py
index aba784b3..65f678b8 100644
--- a/tests/beekeepy_test/handle/api_tests/test_api_set_timeout.py
+++ b/tests/beekeepy_test/handle/api_tests/test_api_set_timeout.py
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
 def test_api_set_timeout(beekeeper: Beekeeper, wallet: WalletInfo) -> None:  # noqa: ARG001
     """Test test_api_set_timeout will test beekeeper_api.set_timeout api call."""
     # ARRANGE
-    bk_wallet = (beekeeper.api.list_wallets()).wallets[0]
+    bk_wallet = (beekeeper.api.list_created_wallets()).wallets[0]
     assert bk_wallet.unlocked is True, "Wallet should be unlocked."
 
     # ACT
@@ -20,5 +20,5 @@ def test_api_set_timeout(beekeeper: Beekeeper, wallet: WalletInfo) -> None:  # n
     time.sleep(1.5)
 
     # ASSERT
-    bk_wallet = (beekeeper.api.list_wallets()).wallets[0]
+    bk_wallet = (beekeeper.api.list_created_wallets()).wallets[0]
     assert bk_wallet.unlocked is False, "Wallet after timeout should be locked."
diff --git a/tests/beekeepy_test/handle/basic/test_wallet.py b/tests/beekeepy_test/handle/basic/test_wallet.py
index 56253a75..ef1eac19 100644
--- a/tests/beekeepy_test/handle/basic/test_wallet.py
+++ b/tests/beekeepy_test/handle/basic/test_wallet.py
@@ -73,13 +73,13 @@ def test_timeout(beekeeper: Beekeeper, wallet: WalletInfo) -> None:
     # ASSERT
     info = beekeeper.api.get_info()
     assert timeout - (info.timeout_time - info.now).total_seconds() <= comparison_error_max_delta
-    check_wallets(beekeeper.api.list_wallets(), [wallet.name])
+    check_wallets(beekeeper.api.list_created_wallets(), [wallet.name])
 
     # ACT
     time.sleep(timeout + 1)
 
     # ASSERT
-    check_wallets(beekeeper.api.list_wallets(), [wallet.name], unlocked=False)
+    check_wallets(beekeeper.api.list_created_wallets(), [wallet.name], unlocked=False)
 
 
 @pytest.mark.parametrize("wallet_name", ["test", "123"])
diff --git a/tests/beekeepy_test/handle/commandline/application_command_line_options/patterns/help_pattern.txt b/tests/beekeepy_test/handle/commandline/application_command_line_options/patterns/help_pattern.txt
index 22331b24..b08615a8 100644
--- a/tests/beekeepy_test/handle/commandline/application_command_line_options/patterns/help_pattern.txt
+++ b/tests/beekeepy_test/handle/commandline/application_command_line_options/patterns/help_pattern.txt
@@ -27,9 +27,6 @@ Application Options:
           --export-keys-wallet "["green-wallet", 
           "PW5KYF9Rt4ETnuP4uheHSCm9kLbCuunf6RqeKgQ8QRoxZmGeZUhhk"]" 
 
-  --notifications-endpoint arg
-          list of addresses, that will receive notification about in-chain 
-          events
   --unlock-interval arg (=500)
           Protection against unlocking by bots. Every wrong `unlock` enables a 
           delay. By default 500[ms].
@@ -62,5 +59,4 @@ Application Command Line Options:
   -d [ --data-dir ] dir
           Directory containing configuration file config.ini. Default location: $HOME/.beekeeper or CWD/. beekeeper
   -c [ --config ] filename (="config.ini")
-          Configuration file name relative to data-dir
-
+          Configuration file name relative to data-dir
\ No newline at end of file
diff --git a/tests/beekeepy_test/handle/commandline/application_options/test_unlock_timeout.py b/tests/beekeepy_test/handle/commandline/application_options/test_unlock_timeout.py
index a04ff79b..0ac7518a 100644
--- a/tests/beekeepy_test/handle/commandline/application_options/test_unlock_timeout.py
+++ b/tests/beekeepy_test/handle/commandline/application_options/test_unlock_timeout.py
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
 
 def check_wallet_lock(beekeeper: Beekeeper, required_status: bool) -> None:
     """Check if wallets are have required unlock status."""
-    response_list_wallets = beekeeper.api.list_wallets()
+    response_list_wallets = beekeeper.api.list_created_wallets()
     for wallet in response_list_wallets.wallets:
         assert wallet.unlocked == required_status
 
diff --git a/tests/beekeepy_test/handle/storage/test_storage.py b/tests/beekeepy_test/handle/storage/test_storage.py
index bdf54514..b5712ba6 100644
--- a/tests/beekeepy_test/handle/storage/test_storage.py
+++ b/tests/beekeepy_test/handle/storage/test_storage.py
@@ -34,14 +34,10 @@ def test_multiply_beekeepeer_same_storage(working_directory: Path) -> None:
         assert bk1.is_running() is True, "First instance of beekeeper should launch without any problems."
 
         # ACT & ASSERT 2
-        bk2 = Beekeeper(settings=settings, logger=logger)
-        with pytest.raises(BeekeeperFailedToStartError):
-            bk2.run()
-
-        assert checkers.check_for_pattern_in_file(
-            bk2.settings.ensured_working_directory / "stderr.log",
-            "Failed to lock access to wallet directory; is another `beekeeper` running?",
-        ), "There should be an info about another instance of beekeeper locking wallet directory."
+        with Beekeeper(settings=settings, logger=logger) as bk2:
+            assert (
+                "opening beekeeper failed" in bk2.apis.app_status.get_app_status().statuses
+            ), "Second instance of beekeeper should fail to start."
 
 
 def test_multiply_beekeepeer_different_storage(working_directory: Path) -> None:
@@ -76,44 +72,12 @@ def test_multiply_beekeepeer_different_storage(working_directory: Path) -> None:
         ), "There should be an no info about another instance of beekeeper locking wallet directory."
 
 
-def get_remote_address_from_connection_file(working_dir: Path) -> HttpUrl:
-    connection: dict[str, str | int] = {}
-    with (working_dir / "beekeeper.connection").open() as file:
-        connection = json.load(file)
-    return HttpUrl(
-        f"{connection['address']}:{connection['port']}",
-        protocol=str(connection["type"]).lower(),  # type: ignore[arg-type]
-    )
-
-
 def test_beekeepers_files_generation(beekeeper: Beekeeper) -> None:
     """Test test_beekeepers_files_generation will check if beekeeper files are generated and have same content."""
     # ARRANGE & ACT
     wallet_dir = beekeeper.settings.ensured_working_directory
-    beekeeper_connection_file = wallet_dir / "beekeeper.connection"
-    beekeeper_pid_file = wallet_dir / "beekeeper.pid"
     beekeeper_wallet_lock_file = wallet_dir / "beekeeper.wallet.lock"
 
     # ASSERT
-    assert beekeeper_connection_file.exists() is True, "File 'beekeeper.connection' should exists"
-    assert beekeeper_pid_file.exists() is True, "File 'beekeeper.pid' should exists"
     # File beekeeper.wallet.lock holds no value inside, so we need only to check is its exists.
     assert beekeeper_wallet_lock_file.exists() is True, "File 'beekeeper.wallet.lock' should exists"
-
-    connection_url = get_remote_address_from_connection_file(wallet_dir)
-    assert connection_url is not None, "There should be connection details."
-
-    if beekeeper.http_endpoint.address == "127.0.0.1":
-        assert connection_url.address in [
-            "0.0.0.0",  # noqa: S104
-            "127.0.0.1",
-        ], "Address should point to localhost or all interfaces."
-    else:
-        assert connection_url.address == beekeeper.http_endpoint.address, "Host should be the same."
-    assert connection_url.port == beekeeper.http_endpoint.port, "Port should be the same."
-    assert connection_url.protocol == beekeeper.http_endpoint.protocol, "Protocol should be the same."
-
-    with Path.open(beekeeper_pid_file) as pid:
-        content = json.load(pid)
-
-        assert content["pid"] == str(beekeeper.pid), "Pid should be the same"
-- 
GitLab


From 24ad4488da66f67abca54eef91dde282efaebf8c Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Wed, 9 Apr 2025 11:28:24 +0000
Subject: [PATCH 15/23] wip

---
 .gitlab-ci.yml | 4 ++--
 hive           | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 666fc2b8..5b86d727 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -34,7 +34,7 @@ variables:
 include:
   - project: 'hive/hive'
     # This has to be the same as the commit checked out in the submodule
-    ref: 40e6bc384b63fe116f370153a20c15a46888011f
+    ref: 89df1f6296f0dba171c96855bca30c5c708acd57
     file: '/scripts/ci-helpers/prepare_data_image_job.yml'
   # DO NOT include ccc here. It will be indirectly included by above yaml file.
   #- project: 'hive/common-ci-configuration'
@@ -101,7 +101,7 @@ build_beekeepy_wheel:
   extends: .build_wheel_template
   needs:
     - job: pre_commit_checks
-    - job: run_beekeepy_tests
+    # - job: run_beekeepy_tests
     - job: prepare_hived_image
       artifacts: true
   variables:
diff --git a/hive b/hive
index 40e6bc38..89df1f62 160000
--- a/hive
+++ b/hive
@@ -1 +1 @@
-Subproject commit 40e6bc384b63fe116f370153a20c15a46888011f
+Subproject commit 89df1f6296f0dba171c96855bca30c5c708acd57
-- 
GitLab


From ae21d226628e993a42de8f56b8934c6a70cd9b57 Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Sat, 12 Apr 2025 19:49:52 +0000
Subject: [PATCH 16/23] Fill missing imports in __init__.py

---
 beekeepy/beekeepy/_executable/__init__.py     | 12 ++--
 beekeepy/beekeepy/_executable/abc/__init__.py | 11 ++--
 beekeepy/beekeepy/_executable/abc/config.py   | 58 ++++++++++++-------
 beekeepy/beekeepy/communication.py            |  2 +
 beekeepy/beekeepy/handle/remote.py            | 26 +++++++++
 beekeepy/beekeepy/handle/runnable.py          | 14 ++++-
 beekeepy/beekeepy/interfaces.py               |  3 +-
 7 files changed, 93 insertions(+), 33 deletions(-)

diff --git a/beekeepy/beekeepy/_executable/__init__.py b/beekeepy/beekeepy/_executable/__init__.py
index 6b444e5d..1f8cfa1f 100644
--- a/beekeepy/beekeepy/_executable/__init__.py
+++ b/beekeepy/beekeepy/_executable/__init__.py
@@ -15,15 +15,15 @@ from beekeepy._executable.beekeeper_executable import BeekeeperExecutable
 from beekeepy._utilities.key_pair import KeyPair
 
 __all__ = [
-    "BeekeeperConfig",
+    "Arguments",
+    "ArgumentT",
     "BeekeeperArguments",
+    "BeekeeperConfig",
     "BeekeeperExecutable",
-    "Arguments",
-    "Executable",
     "Config",
-    "StreamRepresentation",
-    "StreamsHolder",
-    "ArgumentT",
     "ConfigT",
+    "Executable",
     "KeyPair",
+    "StreamRepresentation",
+    "StreamsHolder",
 ]
diff --git a/beekeepy/beekeepy/_executable/abc/__init__.py b/beekeepy/beekeepy/_executable/abc/__init__.py
index 57c9222f..ad16be2e 100644
--- a/beekeepy/beekeepy/_executable/abc/__init__.py
+++ b/beekeepy/beekeepy/_executable/abc/__init__.py
@@ -2,15 +2,16 @@ from __future__ import annotations
 
 from beekeepy._executable.abc.arguments import Arguments
 from beekeepy._executable.abc.config import Config
-from beekeepy._executable.abc.executable import ArgumentT, ConfigT, Executable
+from beekeepy._executable.abc.executable import ArgumentT, AutoCloser, ConfigT, Executable
 from beekeepy._executable.abc.streams import StreamRepresentation, StreamsHolder
 
 __all__ = [
     "Arguments",
-    "Executable",
-    "StreamsHolder",
-    "StreamRepresentation",
-    "Config",
     "ArgumentT",
+    "AutoCloser",
+    "Config",
     "ConfigT",
+    "Executable",
+    "StreamRepresentation",
+    "StreamsHolder",
 ]
diff --git a/beekeepy/beekeepy/_executable/abc/config.py b/beekeepy/beekeepy/_executable/abc/config.py
index 413a43c2..ae4a402f 100644
--- a/beekeepy/beekeepy/_executable/abc/config.py
+++ b/beekeepy/beekeepy/_executable/abc/config.py
@@ -24,33 +24,42 @@ class Config(BaseModel):
         destination = destination / Config.DEFAULT_FILE_NAME if destination.is_dir() else destination
         with destination.open("wt", encoding="utf-8") as out_file:
             out_file.write("# config automatically generated by helpy\n")
-            for member_name, member_value in self.__dict__.items():
-                if member_value is not None:
-                    if isinstance(member_value, list) and len(member_value) == 0:
-                        continue
-
-                    entry_name = self._convert_member_name_to_config_name(member_name)
-                    entry_value = self._convert_member_value_to_config_value(member_name, member_value)
-                    for value in [entry_value] if not isinstance(entry_value, list) else entry_value:
-                        out_file.write(f"{entry_name} = {value}\n")
+            for line in self.write_to_lines():
+                out_file.write(line + "\n")
+
+    def write_to_lines(self) -> list[str]:
+        result = []
+        for member_name, member_value in self.__dict__.items():
+            if member_value is not None:
+                if isinstance(member_value, list) and len(member_value) == 0:
+                    continue
+
+                entry_name = self._convert_member_name_to_config_name(member_name)
+                entry_value = self._convert_member_value_to_config_value(member_name, member_value)
+                for value in [entry_value] if not isinstance(entry_value, list) else entry_value:
+                    result.append(f"{entry_name} = {value}")  # noqa: PERF401  # would be unreadable
+        return result
 
     @classmethod
     def load(cls, source: Path) -> Self:
         source = source / Config.DEFAULT_FILE_NAME if source.is_dir() else source
         assert source.exists(), "Given file does not exists."
+        return cls.load_from_lines(source.read_text().strip().splitlines())
+
+    @classmethod
+    def load_from_lines(cls, lines: list[str]) -> Self:
         fields = cls.__fields__
         values_to_write: dict[str, Any] = {}
-        with source.open("rt", encoding="utf-8") as in_file:
-            for line in in_file:
-                if (line := line.strip("\n")) and not line.startswith("#"):
-                    config_name, config_value = line.split("=")
-                    member_name = cls._convert_config_name_to_member_name(config_name)
-                    member_type = fields[member_name].annotation
-                    if isinstance(member_type, UnionType) and (type(None) in get_args(member_type)):
-                        member_type = get_args(member_type)[0]
-                    values_to_write[member_name] = cls._convert_config_value_to_member_value(
-                        config_value, expected=member_type, current_value=values_to_write.get(member_name)
-                    )
+        for line in lines:
+            if (line := line.strip("\n")) and not line.startswith("#"):
+                config_name, config_value = line.split("=")
+                member_name = cls._convert_config_name_to_member_name(config_name)
+                member_type = fields[member_name].annotation
+                if isinstance(member_type, UnionType) and (type(None) in get_args(member_type)):
+                    member_type = get_args(member_type)[0]
+                values_to_write[member_name] = cls._convert_config_value_to_member_value(
+                    config_value, expected=member_type, current_value=values_to_write.get(member_name)
+                )
         return cls(**values_to_write)
 
     @classmethod
@@ -124,3 +133,12 @@ class Config(BaseModel):
             return expected.validate(config_value)
 
         return expected(config_value) if expected is not None else None
+
+    def get_differences_between(self, other: Self) -> dict[str, tuple[Any, Any]]:
+        differences = {}
+        for member_name in self.__dict__:
+            self_value = getattr(self, member_name)
+            other_value = getattr(other, member_name)
+            if self_value != other_value:
+                differences[member_name] = (self_value, other_value)
+        return differences
diff --git a/beekeepy/beekeepy/communication.py b/beekeepy/beekeepy/communication.py
index 7d2a6fdd..146c92b7 100644
--- a/beekeepy/beekeepy/communication.py
+++ b/beekeepy/beekeepy/communication.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+from beekeepy._communication import rules
 from beekeepy._communication.abc.communicator import AbstractCommunicator
 from beekeepy._communication.abc.overseer import AbstractOverseer
 from beekeepy._communication.abc.rules import OverseerRule, RulesClassifier
@@ -18,6 +19,7 @@ __all__ = [
     "HttpxCommunicator",
     "OverseerRule",
     "RequestCommunicator",
+    "rules",
     "RulesClassifier",
     "StrictOverseer",
 ]
diff --git a/beekeepy/beekeepy/handle/remote.py b/beekeepy/beekeepy/handle/remote.py
index e5330f99..eeb3e652 100644
--- a/beekeepy/beekeepy/handle/remote.py
+++ b/beekeepy/beekeepy/handle/remote.py
@@ -1,11 +1,24 @@
 from __future__ import annotations
 
+from beekeepy._apis import (
+    AppStatusProbeAsyncApiCollection,
+    AppStatusProbeSyncApiCollection,
+    AsyncAppStatusApi,
+    AsyncBeekeeperApi,
+    BeekeeperAsyncApiCollection,
+    BeekeeperSyncApiCollection,
+    SyncAppStatusApi,
+    SyncBeekeeperApi,
+)
 from beekeepy._apis.abc import (
     AbstractAsyncApi,
     AbstractAsyncApiCollection,
     AbstractSyncApi,
     AbstractSyncApiCollection,
+    ApiArgumentSerialization,
+    AsyncSendable,
     RegisteredApisT,
+    SyncSendable,
 )
 from beekeepy._remote_handle import (
     AbstractAsyncHandle,
@@ -16,6 +29,7 @@ from beekeepy._remote_handle import (
     RemoteHandleSettings,
     SyncBatchHandle,
 )
+from beekeepy._remote_handle.abc.handle import RemoteSettingsT
 
 __all__ = [
     "AbstractAsyncApi",
@@ -24,10 +38,22 @@ __all__ = [
     "AbstractSyncApi",
     "AbstractSyncApiCollection",
     "AbstractSyncHandle",
+    "ApiArgumentSerialization",
+    "AppStatusProbeAsyncApiCollection",
+    "AppStatusProbeSyncApiCollection",
+    "AsyncAppStatusApi",
     "AsyncBatchHandle",
     "AsyncBeekeeper",
+    "AsyncBeekeeperApi",
+    "AsyncSendable",
     "Beekeeper",
+    "BeekeeperAsyncApiCollection",
+    "BeekeeperSyncApiCollection",
     "RegisteredApisT",
     "RemoteHandleSettings",
+    "RemoteSettingsT",
+    "SyncAppStatusApi",
     "SyncBatchHandle",
+    "SyncBeekeeperApi",
+    "SyncSendable",
 ]
diff --git a/beekeepy/beekeepy/handle/runnable.py b/beekeepy/beekeepy/handle/runnable.py
index 247ed9e6..a9db11d2 100644
--- a/beekeepy/beekeepy/handle/runnable.py
+++ b/beekeepy/beekeepy/handle/runnable.py
@@ -1,14 +1,26 @@
 from __future__ import annotations
 
 from beekeepy._executable import BeekeeperArguments, BeekeeperConfig, BeekeeperExecutable
+from beekeepy._executable.abc import Arguments, ArgumentT, AutoCloser, Config, ConfigT, Executable
 from beekeepy._runnable_handle import AsyncBeekeeper, Beekeeper, RunnableHandleSettings, close_already_running_beekeeper
+from beekeepy._runnable_handle.match_ports import PortMatchingResult, match_ports
+from beekeepy._runnable_handle.runnable_handle import RunnableHandle
 
 __all__ = [
+    "Arguments",
+    "ArgumentT",
     "AsyncBeekeeper",
+    "AutoCloser",
     "Beekeeper",
-    "BeekeeperConfig",
     "BeekeeperArguments",
+    "BeekeeperConfig",
     "BeekeeperExecutable",
     "close_already_running_beekeeper",
+    "Config",
+    "ConfigT",
+    "Executable",
+    "match_ports",
+    "PortMatchingResult",
+    "RunnableHandle",
     "RunnableHandleSettings",
 ]
diff --git a/beekeepy/beekeepy/interfaces.py b/beekeepy/beekeepy/interfaces.py
index 4b137461..41a299c7 100644
--- a/beekeepy/beekeepy/interfaces.py
+++ b/beekeepy/beekeepy/interfaces.py
@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-from beekeepy._communication import HttpUrl, P2PUrl, Url, WsUrl
+from beekeepy._communication import AnyUrl, HttpUrl, P2PUrl, Url, WsUrl
 from beekeepy._utilities.context import ContextAsync, ContextSync, SelfContextAsync, SelfContextSync
 from beekeepy._utilities.context_settings_updater import ContextSettingsUpdater
 from beekeepy._utilities.delay_guard import AsyncDelayGuard, DelayGuardBase, SyncDelayGuard
@@ -12,6 +12,7 @@ from beekeepy._utilities.stopwatch import Stopwatch, StopwatchResult
 from beekeepy._utilities.suppress_api_not_found import SuppressApiNotFound
 
 __all__ = [
+    "AnyUrl",
     "AsyncDelayGuard",
     "ContextAsync",
     "ContextSettingsUpdater",
-- 
GitLab


From bd0e522351b68a0b87e7ce515b4dbe3139818019 Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Mon, 14 Apr 2025 02:09:54 +0000
Subject: [PATCH 17/23] wip

---
 beekeepy/beekeepy/_executable/abc/config.py | 22 +++++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)

diff --git a/beekeepy/beekeepy/_executable/abc/config.py b/beekeepy/beekeepy/_executable/abc/config.py
index ae4a402f..5793fb71 100644
--- a/beekeepy/beekeepy/_executable/abc/config.py
+++ b/beekeepy/beekeepy/_executable/abc/config.py
@@ -1,8 +1,10 @@
 from __future__ import annotations
 
+import re
+from inspect import isclass
 from pathlib import Path
 from types import UnionType
-from typing import TYPE_CHECKING, Any, ClassVar, get_args
+from typing import TYPE_CHECKING, Any, ClassVar, Final, get_args
 
 from loguru import logger
 from pydantic import BaseModel
@@ -13,6 +15,8 @@ from beekeepy.exceptions import InvalidOptionError
 if TYPE_CHECKING:
     from typing_extensions import Self
 
+CONFIG_MEMBER_REGEX: Final[re.Pattern[str]] = re.compile(r"^([a-zA-Z]+)(\-([a-zA-Z0-9]+))*$")
+
 
 class Config(BaseModel):
     DEFAULT_FILE_NAME: ClassVar[str] = "config.ini"
@@ -49,7 +53,7 @@ class Config(BaseModel):
     @classmethod
     def load_from_lines(cls, lines: list[str]) -> Self:
         fields = cls.__fields__
-        values_to_write: dict[str, Any] = {}
+        values_to_write: dict[str, Any] = cls().dict()
         for line in lines:
             if (line := line.strip("\n")) and not line.startswith("#"):
                 config_name, config_value = line.split("=")
@@ -68,6 +72,9 @@ class Config(BaseModel):
 
     @classmethod
     def _convert_config_name_to_member_name(cls, config_name: str) -> str:
+        config_name = config_name.strip("""" """)
+        if CONFIG_MEMBER_REGEX.match(config_name) is None:
+            raise KeyError(f"""Unknown config entry name: `{config_name}`.""")
         return config_name.strip().replace("-", "_")
 
     @classmethod
@@ -87,7 +94,7 @@ class Config(BaseModel):
         return str(member_value)
 
     @classmethod
-    def _convert_config_value_to_member_value(  # noqa: PLR0911, C901
+    def _convert_config_value_to_member_value(  # noqa: PLR0911, PLR0912, C901
         cls, config_value: str, *, expected: type[Any], current_value: Any | None
     ) -> Any | None:
         config_value = config_value.strip()
@@ -97,7 +104,11 @@ class Config(BaseModel):
         if expected == Path:
             return Path(config_value.replace('"', ""))
 
-        if issubclass(expected, list) or "list" in str(expected):
+        if (
+            (isclass(expected) and issubclass(expected, list))
+            or isinstance(current_value, list)
+            or "list" in str(expected)
+        ):
             list_arg_t = get_args(expected)[0]
             if len(get_args(list_arg_t)):  # in case of unions
                 list_arg_t = get_args(list_arg_t)[0]
@@ -132,6 +143,9 @@ class Config(BaseModel):
         if isinstance(expected, type) and issubclass(expected, int | str) and hasattr(expected, "validate"):
             return expected.validate(config_value)
 
+        if str(expected).startswith("typing.Union["):
+            expected = get_args(expected)[0]
+
         return expected(config_value) if expected is not None else None
 
     def get_differences_between(self, other: Self) -> dict[str, tuple[Any, Any]]:
-- 
GitLab


From 68b2b51f94ab7d1b543b6d9531352af733b629f8 Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Wed, 16 Apr 2025 10:59:32 +0000
Subject: [PATCH 18/23] wip

---
 .pre-commit-config.yaml       |   4 +-
 beekeepy/poetry.lock          |  71 ++++++++++++++++++------
 tests/local-tools/poetry.lock | 100 +++++++++++++++++++++++++++-------
 3 files changed, 135 insertions(+), 40 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d8dea697..aca1dd4d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -14,14 +14,14 @@ repos:
       - id: end-of-file-fixer
         exclude: ".*_pattern.txt|.*wallet"
   - repo: https://github.com/python-poetry/poetry
-    rev: 1.7.1
+    rev: 2.1.2
     hooks:
       - id: poetry-lock
         name: checking if beekeepy/poetry.lock is consistent with pyproject.toml
         args: [ "-C", "./beekeepy", "--no-update" ]
         language_version: python3.12
   - repo: https://github.com/python-poetry/poetry
-    rev: 1.7.1
+    rev: 2.1.2
     hooks:
       - id: poetry-lock
         name: checking if tests/local-tools/poetry.lock is consistent with pyproject.toml
diff --git a/beekeepy/poetry.lock b/beekeepy/poetry.lock
index 21c71f37..ae4da323 100644
--- a/beekeepy/poetry.lock
+++ b/beekeepy/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
 
 [[package]]
 name = "aiohappyeyeballs"
@@ -6,6 +6,7 @@ version = "2.6.1"
 description = "Happy Eyeballs for asyncio"
 optional = false
 python-versions = ">=3.9"
+groups = ["main"]
 files = [
     {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"},
     {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"},
@@ -17,6 +18,7 @@ version = "3.11.16"
 description = "Async http client/server framework (asyncio)"
 optional = false
 python-versions = ">=3.9"
+groups = ["main"]
 files = [
     {file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb46bb0f24813e6cede6cc07b1961d4b04f331f7112a23b5e21f567da4ee50aa"},
     {file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:54eb3aead72a5c19fad07219acd882c1643a1027fbcdefac9b502c267242f955"},
@@ -111,7 +113,7 @@ propcache = ">=0.2.0"
 yarl = ">=1.17.0,<2.0"
 
 [package.extras]
-speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"]
+speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
 
 [[package]]
 name = "aiosignal"
@@ -119,6 +121,7 @@ version = "1.3.1"
 description = "aiosignal: a list of registered asynchronous callbacks"
 optional = false
 python-versions = ">=3.7"
+groups = ["main"]
 files = [
     {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"},
     {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
@@ -133,6 +136,7 @@ version = "4.7.0"
 description = "High level compatibility layer for multiple asynchronous event loop implementations"
 optional = false
 python-versions = ">=3.9"
+groups = ["main"]
 files = [
     {file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"},
     {file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"},
@@ -145,7 +149,7 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
 
 [package.extras]
 doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
-test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
+test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""]
 trio = ["trio (>=0.26.1)"]
 
 [[package]]
@@ -154,18 +158,19 @@ version = "24.2.0"
 description = "Classes Without Boilerplate"
 optional = false
 python-versions = ">=3.7"
+groups = ["main"]
 files = [
     {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"},
     {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"},
 ]
 
 [package.extras]
-benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
-cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
-dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"]
+cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"]
+dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"]
 docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
-tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
-tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
+tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"]
+tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\""]
 
 [[package]]
 name = "certifi"
@@ -173,6 +178,7 @@ version = "2024.8.30"
 description = "Python package for providing Mozilla's CA Bundle."
 optional = false
 python-versions = ">=3.6"
+groups = ["main"]
 files = [
     {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"},
     {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
@@ -184,6 +190,7 @@ version = "2.0.12"
 description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
 optional = false
 python-versions = ">=3.5.0"
+groups = ["main"]
 files = [
     {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
     {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
@@ -198,6 +205,8 @@ version = "0.4.6"
 description = "Cross-platform colored terminal text."
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["main"]
+markers = "sys_platform == \"win32\""
 files = [
     {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
     {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
@@ -209,6 +218,7 @@ version = "1.5.0"
 description = "A list-like structure which implements collections.abc.MutableSequence"
 optional = false
 python-versions = ">=3.8"
+groups = ["main"]
 files = [
     {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"},
     {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"},
@@ -310,6 +320,7 @@ version = "0.14.0"
 description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
 optional = false
 python-versions = ">=3.7"
+groups = ["main"]
 files = [
     {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
     {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
@@ -321,6 +332,7 @@ version = "4.1.0"
 description = "HTTP/2 State-Machine based protocol implementation"
 optional = false
 python-versions = ">=3.6.1"
+groups = ["main"]
 files = [
     {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"},
     {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"},
@@ -336,6 +348,7 @@ version = "4.0.0"
 description = "Pure-Python HPACK header compression"
 optional = false
 python-versions = ">=3.6.1"
+groups = ["main"]
 files = [
     {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"},
     {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"},
@@ -347,6 +360,7 @@ version = "0.16.3"
 description = "A minimal low-level HTTP client."
 optional = false
 python-versions = ">=3.7"
+groups = ["main"]
 files = [
     {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"},
     {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"},
@@ -368,6 +382,7 @@ version = "0.23.3"
 description = "The next generation HTTP client."
 optional = false
 python-versions = ">=3.7"
+groups = ["main"]
 files = [
     {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"},
     {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"},
@@ -381,7 +396,7 @@ rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
 sniffio = "*"
 
 [package.extras]
-brotli = ["brotli", "brotlicffi"]
+brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
 cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<13)"]
 http2 = ["h2 (>=3,<5)"]
 socks = ["socksio (==1.*)"]
@@ -392,6 +407,7 @@ version = "6.0.1"
 description = "HTTP/2 framing layer for Python"
 optional = false
 python-versions = ">=3.6.1"
+groups = ["main"]
 files = [
     {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"},
     {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"},
@@ -403,6 +419,7 @@ version = "3.10"
 description = "Internationalized Domain Names in Applications (IDNA)"
 optional = false
 python-versions = ">=3.6"
+groups = ["main"]
 files = [
     {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
     {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
@@ -417,6 +434,7 @@ version = "0.7.2"
 description = "Python logging made (stupidly) simple"
 optional = false
 python-versions = ">=3.5"
+groups = ["main"]
 files = [
     {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"},
     {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"},
@@ -427,7 +445,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
 win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
 
 [package.extras]
-dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"]
+dev = ["Sphinx (==7.2.5) ; python_version >= \"3.9\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.2.2) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "mypy (==v1.5.1) ; python_version >= \"3.8\"", "pre-commit (==3.4.0) ; python_version >= \"3.8\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==7.4.0) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==4.1.0) ; python_version >= \"3.8\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.0.0) ; python_version >= \"3.8\"", "sphinx-autobuild (==2021.3.14) ; python_version >= \"3.9\"", "sphinx-rtd-theme (==1.3.0) ; python_version >= \"3.9\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.11.0) ; python_version >= \"3.8\""]
 
 [[package]]
 name = "multidict"
@@ -435,6 +453,7 @@ version = "6.1.0"
 description = "multidict implementation"
 optional = false
 python-versions = ">=3.8"
+groups = ["main"]
 files = [
     {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"},
     {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"},
@@ -536,6 +555,7 @@ version = "0.2.1"
 description = "Accelerated property cache"
 optional = false
 python-versions = ">=3.9"
+groups = ["main"]
 files = [
     {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6b3f39a85d671436ee3d12c017f8fdea38509e4f25b28eb25877293c98c243f6"},
     {file = "propcache-0.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d51fbe4285d5db5d92a929e3e21536ea3dd43732c5b177c7ef03f918dff9f2"},
@@ -627,6 +647,7 @@ version = "7.0.0"
 description = "Cross-platform lib for process and system monitoring in Python.  NOTE: the syntax of this script MUST be kept compatible with Python 2.7."
 optional = false
 python-versions = ">=3.6"
+groups = ["main"]
 files = [
     {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"},
     {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"},
@@ -650,6 +671,7 @@ version = "1.10.18"
 description = "Data validation and settings management using python type hints"
 optional = false
 python-versions = ">=3.7"
+groups = ["main"]
 files = [
     {file = "pydantic-1.10.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e405ffcc1254d76bb0e760db101ee8916b620893e6edfbfee563b3c6f7a67c02"},
     {file = "pydantic-1.10.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e306e280ebebc65040034bff1a0a81fd86b2f4f05daac0131f29541cafd80b80"},
@@ -709,6 +731,7 @@ version = "2.8.2"
 description = "Extensions to the standard Python datetime module"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["main"]
 files = [
     {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
     {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
@@ -723,6 +746,7 @@ version = "2.32.3"
 description = "Python HTTP for Humans."
 optional = false
 python-versions = ">=3.8"
+groups = ["main"]
 files = [
     {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
     {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
@@ -744,6 +768,7 @@ version = "1.5.0"
 description = "Validating URI References per RFC 3986"
 optional = false
 python-versions = "*"
+groups = ["main"]
 files = [
     {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
     {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
@@ -761,6 +786,7 @@ version = "0.0.1.dev336+c6f2974"
 description = "Tools for checking if message fits expected format"
 optional = false
 python-versions = ">=3.12,<4.0"
+groups = ["main"]
 files = [
     {file = "schemas-0.0.1.dev336+c6f2974-py3-none-any.whl", hash = "sha256:9a2148f014bdf82e05d138629df11cb20f53799eb32dcfff14942b4b20a76cc6"},
 ]
@@ -779,19 +805,20 @@ version = "77.0.3"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
 optional = false
 python-versions = ">=3.9"
+groups = ["main"]
 files = [
     {file = "setuptools-77.0.3-py3-none-any.whl", hash = "sha256:67122e78221da5cf550ddd04cf8742c8fe12094483749a792d56cd669d6cf58c"},
     {file = "setuptools-77.0.3.tar.gz", hash = "sha256:583b361c8da8de57403743e756609670de6fb2345920e36dc5c2d914c319c945"},
 ]
 
 [package.extras]
-check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"]
-core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""]
+core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"]
 cover = ["pytest-cov"]
 doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
 enabler = ["pytest-enabler (>=2.2)"]
-test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
-type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"]
+test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
+type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"]
 
 [[package]]
 name = "six"
@@ -799,6 +826,7 @@ version = "1.17.0"
 description = "Python 2 and 3 compatibility utilities"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["main"]
 files = [
     {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
     {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
@@ -810,6 +838,7 @@ version = "1.3.1"
 description = "Sniff out which async library your code is running under"
 optional = false
 python-versions = ">=3.7"
+groups = ["main"]
 files = [
     {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
     {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
@@ -821,6 +850,7 @@ version = "76.0.0.20250313"
 description = "Typing stubs for setuptools"
 optional = false
 python-versions = ">=3.9"
+groups = ["main"]
 files = [
     {file = "types_setuptools-76.0.0.20250313-py3-none-any.whl", hash = "sha256:bf454b2a49b8cfd7ebcf5844d4dd5fe4c8666782df1e3663c5866fd51a47460e"},
     {file = "types_setuptools-76.0.0.20250313.tar.gz", hash = "sha256:b2be66f550f95f3cad2a7d46177b273c7e9c80df7d257fa57addbbcfc8126a9e"},
@@ -835,6 +865,7 @@ version = "4.12.2"
 description = "Backported and Experimental Type Hints for Python 3.8+"
 optional = false
 python-versions = ">=3.8"
+groups = ["main"]
 files = [
     {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
     {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
@@ -846,14 +877,15 @@ version = "1.26.20"
 description = "HTTP library with thread-safe connection pooling, file post, and more."
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
+groups = ["main"]
 files = [
     {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"},
     {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"},
 ]
 
 [package.extras]
-brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
-secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
+brotli = ["brotli (==1.0.9) ; os_name != \"nt\" and python_version < \"3\" and platform_python_implementation == \"CPython\"", "brotli (>=1.0.9) ; python_version >= \"3\" and platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; (os_name != \"nt\" or python_version >= \"3\") and platform_python_implementation != \"CPython\"", "brotlipy (>=0.6.0) ; os_name == \"nt\" and python_version < \"3\""]
+secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress ; python_version == \"2.7\"", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
 socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
 
 [[package]]
@@ -862,13 +894,15 @@ version = "1.2.0"
 description = "A small Python utility to set file creation time on Windows"
 optional = false
 python-versions = ">=3.5"
+groups = ["main"]
+markers = "sys_platform == \"win32\""
 files = [
     {file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"},
     {file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"},
 ]
 
 [package.extras]
-dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
+dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"]
 
 [[package]]
 name = "yarl"
@@ -876,6 +910,7 @@ version = "1.18.3"
 description = "Yet another URL library"
 optional = false
 python-versions = ">=3.9"
+groups = ["main"]
 files = [
     {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"},
     {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"},
@@ -967,6 +1002,6 @@ multidict = ">=4.0"
 propcache = ">=0.2.0"
 
 [metadata]
-lock-version = "2.0"
+lock-version = "2.1"
 python-versions = "^3.12"
 content-hash = "82e955944afb54dfa4727975524023ccae164af25866f36871b7ae34129ccdd8"
diff --git a/tests/local-tools/poetry.lock b/tests/local-tools/poetry.lock
index 9db12e17..106acc48 100644
--- a/tests/local-tools/poetry.lock
+++ b/tests/local-tools/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
 
 [[package]]
 name = "aiohappyeyeballs"
@@ -6,6 +6,7 @@ version = "2.6.1"
 description = "Happy Eyeballs for asyncio"
 optional = false
 python-versions = ">=3.9"
+groups = ["main"]
 files = [
     {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"},
     {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"},
@@ -17,6 +18,7 @@ version = "3.11.16"
 description = "Async http client/server framework (asyncio)"
 optional = false
 python-versions = ">=3.9"
+groups = ["main"]
 files = [
     {file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb46bb0f24813e6cede6cc07b1961d4b04f331f7112a23b5e21f567da4ee50aa"},
     {file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:54eb3aead72a5c19fad07219acd882c1643a1027fbcdefac9b502c267242f955"},
@@ -111,7 +113,7 @@ propcache = ">=0.2.0"
 yarl = ">=1.17.0,<2.0"
 
 [package.extras]
-speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"]
+speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
 
 [[package]]
 name = "aiosignal"
@@ -119,6 +121,7 @@ version = "1.3.1"
 description = "aiosignal: a list of registered asynchronous callbacks"
 optional = false
 python-versions = ">=3.7"
+groups = ["main"]
 files = [
     {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"},
     {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"},
@@ -133,6 +136,7 @@ version = "4.4.0"
 description = "High level compatibility layer for multiple asynchronous event loop implementations"
 optional = false
 python-versions = ">=3.8"
+groups = ["main"]
 files = [
     {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"},
     {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"},
@@ -144,7 +148,7 @@ sniffio = ">=1.1"
 
 [package.extras]
 doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
-test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
+test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\""]
 trio = ["trio (>=0.23)"]
 
 [[package]]
@@ -153,18 +157,19 @@ version = "24.2.0"
 description = "Classes Without Boilerplate"
 optional = false
 python-versions = ">=3.7"
+groups = ["main"]
 files = [
     {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"},
     {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"},
 ]
 
 [package.extras]
-benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
-cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
-dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
+benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"]
+cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"]
+dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"]
 docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
-tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
-tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
+tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\"", "pytest-xdist[psutil]"]
+tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.9\" and python_version < \"3.13\""]
 
 [[package]]
 name = "beekeepy"
@@ -172,6 +177,7 @@ version = "0.0.0"
 description = "All in one package for beekeeper interaction via Python interface."
 optional = false
 python-versions = "^3.12"
+groups = ["main"]
 files = []
 develop = true
 
@@ -196,6 +202,7 @@ version = "2024.8.30"
 description = "Python package for providing Mozilla's CA Bundle."
 optional = false
 python-versions = ">=3.6"
+groups = ["main"]
 files = [
     {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"},
     {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
@@ -207,6 +214,7 @@ version = "3.4.0"
 description = "Validate configuration and produce human readable error messages."
 optional = false
 python-versions = ">=3.8"
+groups = ["dev"]
 files = [
     {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
     {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
@@ -218,6 +226,7 @@ version = "2.0.12"
 description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
 optional = false
 python-versions = ">=3.5.0"
+groups = ["main"]
 files = [
     {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
     {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
@@ -232,6 +241,8 @@ version = "0.4.6"
 description = "Cross-platform colored terminal text."
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["main", "dev"]
+markers = "sys_platform == \"win32\""
 files = [
     {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
     {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
@@ -243,6 +254,7 @@ version = "0.3.8"
 description = "Distribution utilities"
 optional = false
 python-versions = "*"
+groups = ["dev"]
 files = [
     {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"},
     {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
@@ -254,6 +266,7 @@ version = "2.1.1"
 description = "execnet: rapid multi-Python deployment"
 optional = false
 python-versions = ">=3.8"
+groups = ["dev"]
 files = [
     {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"},
     {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"},
@@ -268,6 +281,7 @@ version = "3.16.1"
 description = "A platform independent file lock."
 optional = false
 python-versions = ">=3.8"
+groups = ["dev"]
 files = [
     {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"},
     {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"},
@@ -276,7 +290,7 @@ files = [
 [package.extras]
 docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"]
 testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"]
-typing = ["typing-extensions (>=4.12.2)"]
+typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""]
 
 [[package]]
 name = "frozenlist"
@@ -284,6 +298,7 @@ version = "1.4.1"
 description = "A list-like structure which implements collections.abc.MutableSequence"
 optional = false
 python-versions = ">=3.8"
+groups = ["main"]
 files = [
     {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"},
     {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"},
@@ -370,6 +385,7 @@ version = "0.14.0"
 description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
 optional = false
 python-versions = ">=3.7"
+groups = ["main"]
 files = [
     {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
     {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
@@ -381,6 +397,7 @@ version = "4.1.0"
 description = "HTTP/2 State-Machine based protocol implementation"
 optional = false
 python-versions = ">=3.6.1"
+groups = ["main"]
 files = [
     {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"},
     {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"},
@@ -396,6 +413,7 @@ version = "4.0.0"
 description = "Pure-Python HPACK header compression"
 optional = false
 python-versions = ">=3.6.1"
+groups = ["main"]
 files = [
     {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"},
     {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"},
@@ -407,6 +425,7 @@ version = "0.16.3"
 description = "A minimal low-level HTTP client."
 optional = false
 python-versions = ">=3.7"
+groups = ["main"]
 files = [
     {file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"},
     {file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"},
@@ -428,6 +447,7 @@ version = "0.23.3"
 description = "The next generation HTTP client."
 optional = false
 python-versions = ">=3.7"
+groups = ["main"]
 files = [
     {file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"},
     {file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"},
@@ -441,7 +461,7 @@ rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
 sniffio = "*"
 
 [package.extras]
-brotli = ["brotli", "brotlicffi"]
+brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
 cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<13)"]
 http2 = ["h2 (>=3,<5)"]
 socks = ["socksio (==1.*)"]
@@ -452,6 +472,7 @@ version = "6.0.1"
 description = "HTTP/2 framing layer for Python"
 optional = false
 python-versions = ">=3.6.1"
+groups = ["main"]
 files = [
     {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"},
     {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"},
@@ -463,6 +484,7 @@ version = "2.6.1"
 description = "File identification library for Python"
 optional = false
 python-versions = ">=3.8"
+groups = ["dev"]
 files = [
     {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"},
     {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"},
@@ -477,6 +499,7 @@ version = "3.10"
 description = "Internationalized Domain Names in Applications (IDNA)"
 optional = false
 python-versions = ">=3.6"
+groups = ["main"]
 files = [
     {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
     {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
@@ -491,6 +514,7 @@ version = "2.0.0"
 description = "brain-dead simple config-ini parsing"
 optional = false
 python-versions = ">=3.7"
+groups = ["dev"]
 files = [
     {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
     {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
@@ -502,6 +526,7 @@ version = "0.7.2"
 description = "Python logging made (stupidly) simple"
 optional = false
 python-versions = ">=3.5"
+groups = ["main"]
 files = [
     {file = "loguru-0.7.2-py3-none-any.whl", hash = "sha256:003d71e3d3ed35f0f8984898359d65b79e5b21943f78af86aa5491210429b8eb"},
     {file = "loguru-0.7.2.tar.gz", hash = "sha256:e671a53522515f34fd406340ee968cb9ecafbc4b36c679da03c18fd8d0bd51ac"},
@@ -512,7 +537,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
 win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
 
 [package.extras]
-dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"]
+dev = ["Sphinx (==7.2.5) ; python_version >= \"3.9\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.2.2) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "mypy (==v1.5.1) ; python_version >= \"3.8\"", "pre-commit (==3.4.0) ; python_version >= \"3.8\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==7.4.0) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==4.1.0) ; python_version >= \"3.8\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.0.0) ; python_version >= \"3.8\"", "sphinx-autobuild (==2021.3.14) ; python_version >= \"3.9\"", "sphinx-rtd-theme (==1.3.0) ; python_version >= \"3.9\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.11.0) ; python_version >= \"3.8\""]
 
 [[package]]
 name = "multidict"
@@ -520,6 +545,7 @@ version = "6.1.0"
 description = "multidict implementation"
 optional = false
 python-versions = ">=3.8"
+groups = ["main"]
 files = [
     {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"},
     {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"},
@@ -621,6 +647,7 @@ version = "1.11.2"
 description = "Optional static typing for Python"
 optional = false
 python-versions = ">=3.8"
+groups = ["dev"]
 files = [
     {file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"},
     {file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"},
@@ -667,6 +694,7 @@ version = "1.0.0"
 description = "Type system extensions for programs checked with the mypy type checker."
 optional = false
 python-versions = ">=3.5"
+groups = ["dev"]
 files = [
     {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
     {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
@@ -678,6 +706,7 @@ version = "1.9.1"
 description = "Node.js virtual environment builder"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["dev"]
 files = [
     {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"},
     {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
@@ -689,6 +718,7 @@ version = "24.1"
 description = "Core utilities for Python packages"
 optional = false
 python-versions = ">=3.8"
+groups = ["dev"]
 files = [
     {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
     {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
@@ -700,6 +730,7 @@ version = "4.3.6"
 description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
 optional = false
 python-versions = ">=3.8"
+groups = ["dev"]
 files = [
     {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
     {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
@@ -716,6 +747,7 @@ version = "1.5.0"
 description = "plugin and hook calling mechanisms for python"
 optional = false
 python-versions = ">=3.8"
+groups = ["dev"]
 files = [
     {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
     {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
@@ -731,6 +763,7 @@ version = "2.21.0"
 description = "A framework for managing and maintaining multi-language pre-commit hooks."
 optional = false
 python-versions = ">=3.7"
+groups = ["dev"]
 files = [
     {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"},
     {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"},
@@ -749,6 +782,7 @@ version = "0.3.0"
 description = "Accelerated property cache"
 optional = false
 python-versions = ">=3.9"
+groups = ["main"]
 files = [
     {file = "propcache-0.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:efa44f64c37cc30c9f05932c740a8b40ce359f51882c70883cc95feac842da4d"},
     {file = "propcache-0.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2383a17385d9800b6eb5855c2f05ee550f803878f344f58b6e194de08b96352c"},
@@ -856,6 +890,7 @@ version = "7.0.0"
 description = "Cross-platform lib for process and system monitoring in Python.  NOTE: the syntax of this script MUST be kept compatible with Python 2.7."
 optional = false
 python-versions = ">=3.6"
+groups = ["main"]
 files = [
     {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"},
     {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"},
@@ -879,6 +914,7 @@ version = "1.10.18"
 description = "Data validation and settings management using python type hints"
 optional = false
 python-versions = ">=3.7"
+groups = ["main"]
 files = [
     {file = "pydantic-1.10.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e405ffcc1254d76bb0e760db101ee8916b620893e6edfbfee563b3c6f7a67c02"},
     {file = "pydantic-1.10.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e306e280ebebc65040034bff1a0a81fd86b2f4f05daac0131f29541cafd80b80"},
@@ -938,6 +974,7 @@ version = "8.3.5"
 description = "pytest: simple powerful testing with Python"
 optional = false
 python-versions = ">=3.8"
+groups = ["dev"]
 files = [
     {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"},
     {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"},
@@ -958,6 +995,7 @@ version = "0.25.3"
 description = "Pytest support for asyncio"
 optional = false
 python-versions = ">=3.9"
+groups = ["dev"]
 files = [
     {file = "pytest_asyncio-0.25.3-py3-none-any.whl", hash = "sha256:9e89518e0f9bd08928f97a3482fdc4e244df17529460bc038291ccaf8f85c7c3"},
     {file = "pytest_asyncio-0.25.3.tar.gz", hash = "sha256:fc1da2cf9f125ada7e710b4ddad05518d4cee187ae9412e9ac9271003497f07a"},
@@ -976,6 +1014,7 @@ version = "3.6.1"
 description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs"
 optional = false
 python-versions = ">=3.8"
+groups = ["dev"]
 files = [
     {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"},
     {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"},
@@ -996,6 +1035,7 @@ version = "2.8.2"
 description = "Extensions to the standard Python datetime module"
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["main"]
 files = [
     {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
     {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
@@ -1010,6 +1050,7 @@ version = "6.0.2"
 description = "YAML parser and emitter for Python"
 optional = false
 python-versions = ">=3.8"
+groups = ["dev"]
 files = [
     {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
     {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
@@ -1072,6 +1113,7 @@ version = "2.32.3"
 description = "Python HTTP for Humans."
 optional = false
 python-versions = ">=3.8"
+groups = ["main"]
 files = [
     {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"},
     {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"},
@@ -1093,6 +1135,7 @@ version = "1.5.0"
 description = "Validating URI References per RFC 3986"
 optional = false
 python-versions = "*"
+groups = ["main"]
 files = [
     {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
     {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
@@ -1110,6 +1153,7 @@ version = "0.6.5"
 description = "An extremely fast Python linter and code formatter, written in Rust."
 optional = false
 python-versions = ">=3.7"
+groups = ["dev"]
 files = [
     {file = "ruff-0.6.5-py3-none-linux_armv6l.whl", hash = "sha256:7e4e308f16e07c95fc7753fc1aaac690a323b2bb9f4ec5e844a97bb7fbebd748"},
     {file = "ruff-0.6.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:932cd69eefe4daf8c7d92bd6689f7e8182571cb934ea720af218929da7bd7d69"},
@@ -1137,6 +1181,7 @@ version = "0.0.1.dev336+c6f2974"
 description = "Tools for checking if message fits expected format"
 optional = false
 python-versions = ">=3.12,<4.0"
+groups = ["main"]
 files = [
     {file = "schemas-0.0.1.dev336+c6f2974-py3-none-any.whl", hash = "sha256:9a2148f014bdf82e05d138629df11cb20f53799eb32dcfff14942b4b20a76cc6"},
 ]
@@ -1155,19 +1200,20 @@ version = "77.0.3"
 description = "Easily download, build, install, upgrade, and uninstall Python packages"
 optional = false
 python-versions = ">=3.9"
+groups = ["main"]
 files = [
     {file = "setuptools-77.0.3-py3-none-any.whl", hash = "sha256:67122e78221da5cf550ddd04cf8742c8fe12094483749a792d56cd669d6cf58c"},
     {file = "setuptools-77.0.3.tar.gz", hash = "sha256:583b361c8da8de57403743e756609670de6fb2345920e36dc5c2d914c319c945"},
 ]
 
 [package.extras]
-check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"]
-core = ["importlib_metadata (>=6)", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""]
+core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"]
 cover = ["pytest-cov"]
 doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
 enabler = ["pytest-enabler (>=2.2)"]
-test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
-type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"]
+test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
+type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"]
 
 [[package]]
 name = "six"
@@ -1175,6 +1221,7 @@ version = "1.16.0"
 description = "Python 2 and 3 compatibility utilities"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+groups = ["main"]
 files = [
     {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
     {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
@@ -1186,6 +1233,7 @@ version = "1.3.1"
 description = "Sniff out which async library your code is running under"
 optional = false
 python-versions = ">=3.7"
+groups = ["main"]
 files = [
     {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
     {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
@@ -1197,6 +1245,7 @@ version = "6.0.0.20240901"
 description = "Typing stubs for psutil"
 optional = false
 python-versions = ">=3.8"
+groups = ["dev"]
 files = [
     {file = "types-psutil-6.0.0.20240901.tar.gz", hash = "sha256:437affa76670363db9ffecfa4f153cc6900bf8a7072b3420f3bc07a593f92226"},
     {file = "types_psutil-6.0.0.20240901-py3-none-any.whl", hash = "sha256:20af311bfb0386a018a27ae47dc952119d7c0e849ff72b6aa24fc0433afb92a6"},
@@ -1208,6 +1257,7 @@ version = "2.8.19.14"
 description = "Typing stubs for python-dateutil"
 optional = false
 python-versions = "*"
+groups = ["dev"]
 files = [
     {file = "types-python-dateutil-2.8.19.14.tar.gz", hash = "sha256:1f4f10ac98bb8b16ade9dbee3518d9ace017821d94b057a425b069f834737f4b"},
     {file = "types_python_dateutil-2.8.19.14-py3-none-any.whl", hash = "sha256:f977b8de27787639986b4e28963263fd0e5158942b3ecef91b9335c130cb1ce9"},
@@ -1219,6 +1269,7 @@ version = "6.0.12.4"
 description = "Typing stubs for PyYAML"
 optional = false
 python-versions = "*"
+groups = ["dev"]
 files = [
     {file = "types-PyYAML-6.0.12.4.tar.gz", hash = "sha256:ade6e328a5a3df816c47c912c2e1e946ae2bace90744aa73111ee6834b03a314"},
     {file = "types_PyYAML-6.0.12.4-py3-none-any.whl", hash = "sha256:de3bacfc4e0772d9b1baf007c37354f3c34c8952e90307d5155b6de0fc183a67"},
@@ -1230,6 +1281,7 @@ version = "2.31.0.2"
 description = "Typing stubs for requests"
 optional = false
 python-versions = "*"
+groups = ["dev"]
 files = [
     {file = "types-requests-2.31.0.2.tar.gz", hash = "sha256:6aa3f7faf0ea52d728bb18c0a0d1522d9bfd8c72d26ff6f61bfc3d06a411cf40"},
     {file = "types_requests-2.31.0.2-py3-none-any.whl", hash = "sha256:56d181c85b5925cbc59f4489a57e72a8b2166f18273fd8ba7b6fe0c0b986f12a"},
@@ -1244,6 +1296,7 @@ version = "76.0.0.20250313"
 description = "Typing stubs for setuptools"
 optional = false
 python-versions = ">=3.9"
+groups = ["main"]
 files = [
     {file = "types_setuptools-76.0.0.20250313-py3-none-any.whl", hash = "sha256:bf454b2a49b8cfd7ebcf5844d4dd5fe4c8666782df1e3663c5866fd51a47460e"},
     {file = "types_setuptools-76.0.0.20250313.tar.gz", hash = "sha256:b2be66f550f95f3cad2a7d46177b273c7e9c80df7d257fa57addbbcfc8126a9e"},
@@ -1258,6 +1311,7 @@ version = "1.26.25.14"
 description = "Typing stubs for urllib3"
 optional = false
 python-versions = "*"
+groups = ["dev"]
 files = [
     {file = "types-urllib3-1.26.25.14.tar.gz", hash = "sha256:229b7f577c951b8c1b92c1bc2b2fdb0b49847bd2af6d1cc2a2e3dd340f3bda8f"},
     {file = "types_urllib3-1.26.25.14-py3-none-any.whl", hash = "sha256:9683bbb7fb72e32bfe9d2be6e04875fbe1b3eeec3cbb4ea231435aa7fd6b4f0e"},
@@ -1269,6 +1323,7 @@ version = "4.12.2"
 description = "Backported and Experimental Type Hints for Python 3.8+"
 optional = false
 python-versions = ">=3.8"
+groups = ["main", "dev"]
 files = [
     {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
     {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
@@ -1280,14 +1335,15 @@ version = "1.26.20"
 description = "HTTP library with thread-safe connection pooling, file post, and more."
 optional = false
 python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
+groups = ["main"]
 files = [
     {file = "urllib3-1.26.20-py2.py3-none-any.whl", hash = "sha256:0ed14ccfbf1c30a9072c7ca157e4319b70d65f623e91e7b32fadb2853431016e"},
     {file = "urllib3-1.26.20.tar.gz", hash = "sha256:40c2dc0c681e47eb8f90e7e27bf6ff7df2e677421fd46756da1161c39ca70d32"},
 ]
 
 [package.extras]
-brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
-secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
+brotli = ["brotli (==1.0.9) ; os_name != \"nt\" and python_version < \"3\" and platform_python_implementation == \"CPython\"", "brotli (>=1.0.9) ; python_version >= \"3\" and platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; (os_name != \"nt\" or python_version >= \"3\") and platform_python_implementation != \"CPython\"", "brotlipy (>=0.6.0) ; os_name == \"nt\" and python_version < \"3\""]
+secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress ; python_version == \"2.7\"", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
 socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
 
 [[package]]
@@ -1296,6 +1352,7 @@ version = "20.26.5"
 description = "Virtual Python Environment builder"
 optional = false
 python-versions = ">=3.7"
+groups = ["dev"]
 files = [
     {file = "virtualenv-20.26.5-py3-none-any.whl", hash = "sha256:4f3ac17b81fba3ce3bd6f4ead2749a72da5929c01774948e243db9ba41df4ff6"},
     {file = "virtualenv-20.26.5.tar.gz", hash = "sha256:ce489cac131aa58f4b25e321d6d186171f78e6cb13fafbf32a840cee67733ff4"},
@@ -1308,7 +1365,7 @@ platformdirs = ">=3.9.1,<5"
 
 [package.extras]
 docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
-test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
+test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""]
 
 [[package]]
 name = "win32-setctime"
@@ -1316,13 +1373,15 @@ version = "1.1.0"
 description = "A small Python utility to set file creation time on Windows"
 optional = false
 python-versions = ">=3.5"
+groups = ["main"]
+markers = "sys_platform == \"win32\""
 files = [
     {file = "win32_setctime-1.1.0-py3-none-any.whl", hash = "sha256:231db239e959c2fe7eb1d7dc129f11172354f98361c4fa2d6d2d7e278baa8aad"},
     {file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
 ]
 
 [package.extras]
-dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
+dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"]
 
 [[package]]
 name = "yarl"
@@ -1330,6 +1389,7 @@ version = "1.18.3"
 description = "Yet another URL library"
 optional = false
 python-versions = ">=3.9"
+groups = ["main"]
 files = [
     {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"},
     {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"},
@@ -1421,6 +1481,6 @@ multidict = ">=4.0"
 propcache = ">=0.2.0"
 
 [metadata]
-lock-version = "2.0"
+lock-version = "2.1"
 python-versions = "^3.12"
 content-hash = "a7125ab9d77d3431d5076460d8ebb6d574bc96408318c0f6fa02df550b633eab"
-- 
GitLab


From c23bd7002059f444dc79458e372903a2135a3e5e Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Wed, 16 Apr 2025 11:05:06 +0000
Subject: [PATCH 19/23] wip

---
 .pre-commit-config.yaml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index aca1dd4d..b4de8c5e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -18,14 +18,14 @@ repos:
     hooks:
       - id: poetry-lock
         name: checking if beekeepy/poetry.lock is consistent with pyproject.toml
-        args: [ "-C", "./beekeepy", "--no-update" ]
+        args: [ "-C", "./beekeepy" ]
         language_version: python3.12
   - repo: https://github.com/python-poetry/poetry
     rev: 2.1.2
     hooks:
       - id: poetry-lock
         name: checking if tests/local-tools/poetry.lock is consistent with pyproject.toml
-        args: [ "-C", "./tests/local-tools", "--no-update" ]
+        args: [ "-C", "./tests/local-tools" ]
         language_version: python3.12
   - repo: https://github.com/charliermarsh/ruff-pre-commit
     rev: 'v0.6.5'
-- 
GitLab


From a9ee4c1bb0664045ca733bcfbf53c1d7666d3fdd Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Wed, 16 Apr 2025 11:51:01 +0000
Subject: [PATCH 20/23] wp

---
 hive | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/hive b/hive
index 89df1f62..bcf663ac 160000
--- a/hive
+++ b/hive
@@ -1 +1 @@
-Subproject commit 89df1f6296f0dba171c96855bca30c5c708acd57
+Subproject commit bcf663ac828958d77f4510dcefb885dc371575bb
-- 
GitLab


From 01bcfa6205e9a62edf56299468791a405ea8cd52 Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Thu, 17 Apr 2025 11:59:55 +0000
Subject: [PATCH 21/23] wip

---
 .../_runnable_handle/runnable_handle.py       | 22 +++++++++++++------
 1 file changed, 15 insertions(+), 7 deletions(-)

diff --git a/beekeepy/beekeepy/_runnable_handle/runnable_handle.py b/beekeepy/beekeepy/_runnable_handle/runnable_handle.py
index 5efd171f..11d05f50 100644
--- a/beekeepy/beekeepy/_runnable_handle/runnable_handle.py
+++ b/beekeepy/beekeepy/_runnable_handle/runnable_handle.py
@@ -20,6 +20,7 @@ from beekeepy.exceptions import (
     FailedToDetectReservedPortsError,
     FailedToStartExecutableError,
 )
+from beekeepy.interfaces import Stopwatch
 
 if TYPE_CHECKING:
     from loguru import Logger
@@ -120,10 +121,10 @@ class RunnableHandle(ABC, Generic[ExecutableT, ConfigT, ArgumentT, SettingsT]):
         except SubprocessError as e:
             raise FailedToStartExecutableError from e
         try:
-            self._wait_for_app_to_start()
+            ports = self._wait_for_app_to_start()
         except TimeoutError as e:
             raise FailedToDetectReservedPortsError from e
-        self._setup_ports(self.__discover_ports())
+        self._setup_ports(ports)
 
     @abstractmethod
     def _construct_executable(self) -> ExecutableT:
@@ -177,12 +178,19 @@ class RunnableHandle(ABC, Generic[ExecutableT, ConfigT, ArgumentT, SettingsT]):
             ports -- list of ports reserved by started application.
         """
 
-    def _wait_for_app_to_start(self) -> None:
+    def _wait_for_app_to_start(self) -> PortMatchingResult:
         """Waits for application to start."""
-        while not self._exec.reserved_ports():
-            if not self._exec.is_running():
-                raise FailedToStartExecutableError
-            time.sleep(0.1)
+        with Stopwatch() as stopwatch:
+            while stopwatch.lap <= self._get_settings().initialization_timeout.total_seconds() and not bool(
+                discovered_ports := self.__discover_ports()
+            ):
+                if not self._exec.is_running():
+                    raise FailedToStartExecutableError
+                time.sleep(0.1)
+
+        if bool(discovered_ports):
+            return discovered_ports
+        raise TimeoutError("Timeout while waiting for application to start")
 
     def __choose_working_directory(self, settings: Settings) -> Path:
         return self.__choose_value(
-- 
GitLab


From 6116038346997a727a7577462b15a42832885d29 Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Fri, 18 Apr 2025 10:04:29 +0000
Subject: [PATCH 22/23] wip

---
 beekeepy/beekeepy/_runnable_handle/match_ports.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/beekeepy/beekeepy/_runnable_handle/match_ports.py b/beekeepy/beekeepy/_runnable_handle/match_ports.py
index 09f46fb6..95d37854 100644
--- a/beekeepy/beekeepy/_runnable_handle/match_ports.py
+++ b/beekeepy/beekeepy/_runnable_handle/match_ports.py
@@ -20,6 +20,9 @@ class PortMatchingResult:
     websocket: WsUrl | None = None
     p2p: list[P2PUrl] = field(default_factory=list)
 
+    def __bool__(self) -> bool:
+        return self.http is not None
+
 
 def test_http(address: HttpUrl) -> bool:
     assert address.port is not None, "HTTP CHECK: Port has to be set"
-- 
GitLab


From 28ac56d44fa1ead12f69c6a7184e35165696acb2 Mon Sep 17 00:00:00 2001
From: kmochocki <kmochocki@syncad.com>
Date: Fri, 18 Apr 2025 12:48:20 +0000
Subject: [PATCH 23/23] wip

---
 .../beekeepy/_runnable_handle/runnable_handle.py    | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/beekeepy/beekeepy/_runnable_handle/runnable_handle.py b/beekeepy/beekeepy/_runnable_handle/runnable_handle.py
index 11d05f50..4238ae20 100644
--- a/beekeepy/beekeepy/_runnable_handle/runnable_handle.py
+++ b/beekeepy/beekeepy/_runnable_handle/runnable_handle.py
@@ -1,5 +1,6 @@
 from __future__ import annotations
 
+import contextlib
 import time
 import warnings
 from abc import ABC, abstractmethod
@@ -180,17 +181,23 @@ class RunnableHandle(ABC, Generic[ExecutableT, ConfigT, ArgumentT, SettingsT]):
 
     def _wait_for_app_to_start(self) -> PortMatchingResult:
         """Waits for application to start."""
+
+        def ignore_timeout_error() -> PortMatchingResult | None:
+            with contextlib.suppress(TimeoutError):
+                return self.__discover_ports()
+            return None
+
         with Stopwatch() as stopwatch:
             while stopwatch.lap <= self._get_settings().initialization_timeout.total_seconds() and not bool(
-                discovered_ports := self.__discover_ports()
+                discovered_ports := ignore_timeout_error()
             ):
                 if not self._exec.is_running():
                     raise FailedToStartExecutableError
                 time.sleep(0.1)
 
-        if bool(discovered_ports):
+        if discovered_ports is not None and bool(discovered_ports):
             return discovered_ports
-        raise TimeoutError("Timeout while waiting for application to start")
+        raise TimeoutError(f"Timeout after {stopwatch.seconds_delta :2f} waiting for application to start")
 
     def __choose_working_directory(self, settings: Settings) -> Path:
         return self.__choose_value(
-- 
GitLab