From aa1d7c1b6f0c2fc925603303e371fd2bd761a53e Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 25 Jan 2024 13:50:04 -0700 Subject: [PATCH] refactor: make web handlers much more consistent across types, remove graphql, and make luci and grpc the dedicated web apis for BOSer and BOSMiner respectively. --- pyasic/miners/backends/braiins_os.py | 63 +++-- pyasic/miners/base.py | 12 +- pyasic/web/__init__.py | 15 +- pyasic/web/antminer.py | 24 +- pyasic/web/auradine.py | 100 ++++---- pyasic/web/braiins_os/__init__.py | 151 +----------- pyasic/web/braiins_os/{grpc.py => boser.py} | 221 ++++++++++-------- .../web/braiins_os/{luci.py => bosminer.py} | 53 +++-- pyasic/web/braiins_os/graphql.py | 105 --------- pyasic/web/epic.py | 44 ++-- pyasic/web/goldshell.py | 60 ++--- pyasic/web/innosilicon.py | 35 +-- pyasic/web/vnish.py | 54 ++--- 13 files changed, 356 insertions(+), 581 deletions(-) rename pyasic/web/braiins_os/{grpc.py => boser.py} (71%) rename pyasic/web/braiins_os/{luci.py => bosminer.py} (75%) delete mode 100644 pyasic/web/braiins_os/graphql.py diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index b1ff85b7..4a6efd64 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -29,7 +29,6 @@ from pyasic.miners.base import ( DataFunction, DataLocations, DataOptions, - GRPCCommand, RPCAPICommand, WebAPICommand, ) @@ -275,7 +274,7 @@ class BOSMiner(BaseMiner): async def _get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]: if web_net_conf is None: try: - web_net_conf = await self.web.luci.get_net_conf() + web_net_conf = await self.web.get_net_conf() except APIError: pass @@ -314,7 +313,7 @@ class BOSMiner(BaseMiner): async def _get_fw_ver(self, web_bos_info: dict = None) -> Optional[str]: if web_bos_info is None: try: - web_bos_info = await self.web.luci.get_bos_info() + web_bos_info = await self.web.get_bos_info() except APIError: return None @@ -571,19 +570,19 @@ BOSER_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( "_get_mac", - [GRPCCommand("grpc_miner_details", "get_miner_details")], + [WebAPICommand("grpc_miner_details", "get_miner_details")], ), str(DataOptions.API_VERSION): DataFunction( "_get_api_ver", - [GRPCCommand("api_version", "get_api_version")], + [RPCAPICommand("api_version", "version")], ), str(DataOptions.FW_VERSION): DataFunction( "_get_fw_ver", - [GRPCCommand("grpc_miner_details", "get_miner_details")], + [WebAPICommand("grpc_miner_details", "get_miner_details")], ), str(DataOptions.HOSTNAME): DataFunction( "_get_hostname", - [GRPCCommand("grpc_miner_details", "get_miner_details")], + [WebAPICommand("grpc_miner_details", "get_miner_details")], ), str(DataOptions.HASHRATE): DataFunction( "_get_hashrate", @@ -591,27 +590,27 @@ BOSER_DATA_LOC = DataLocations( ), str(DataOptions.EXPECTED_HASHRATE): DataFunction( "_get_expected_hashrate", - [GRPCCommand("grpc_miner_details", "get_miner_details")], + [WebAPICommand("grpc_miner_details", "get_miner_details")], ), str(DataOptions.HASHBOARDS): DataFunction( "_get_hashboards", - [GRPCCommand("grpc_hashboards", "get_hashboards")], + [WebAPICommand("grpc_hashboards", "get_hashboards")], ), str(DataOptions.WATTAGE): DataFunction( "_get_wattage", - [GRPCCommand("grpc_miner_stats", "get_miner_stats")], + [WebAPICommand("grpc_miner_stats", "get_miner_stats")], ), str(DataOptions.WATTAGE_LIMIT): DataFunction( "_get_wattage_limit", [ - GRPCCommand( + WebAPICommand( "grpc_active_performance_mode", "get_active_performance_mode" ) ], ), str(DataOptions.FANS): DataFunction( "_get_fans", - [GRPCCommand("grpc_cooling_state", "get_cooling_state")], + [WebAPICommand("grpc_cooling_state", "get_cooling_state")], ), str(DataOptions.ERRORS): DataFunction( "_get_errors", @@ -619,7 +618,7 @@ BOSER_DATA_LOC = DataLocations( ), str(DataOptions.FAULT_LIGHT): DataFunction( "_get_fault_light", - [GRPCCommand("grpc_locate_device_status", "get_locate_device_status")], + [WebAPICommand("grpc_locate_device_status", "get_locate_device_status")], ), str(DataOptions.IS_MINING): DataFunction( "_is_mining", @@ -647,13 +646,13 @@ class BOSer(BaseMiner): supports_shutdown = True async def fault_light_on(self) -> bool: - resp = await self.web.grpc.set_locate_device_status(True) + resp = await self.web.set_locate_device_status(True) if resp.get("enabled", False): return True return False async def fault_light_off(self) -> bool: - resp = await self.web.grpc.set_locate_device_status(False) + resp = await self.web.set_locate_device_status(False) if resp == {}: return True return False @@ -662,37 +661,37 @@ class BOSer(BaseMiner): return await self.restart_boser() async def restart_boser(self) -> bool: - await self.web.grpc.restart() + await self.web.restart() return True async def stop_mining(self) -> bool: try: - await self.web.grpc.pause_mining() + await self.web.pause_mining() except APIError: return False return True async def resume_mining(self) -> bool: try: - await self.web.grpc.resume_mining() + await self.web.resume_mining() except APIError: return False return True async def reboot(self) -> bool: - ret = await self.web.grpc.reboot() + ret = await self.web.reboot() if ret == {}: return True return False async def get_config(self) -> MinerConfig: - grpc_conf = await self.web.grpc.get_miner_configuration() + grpc_conf = await self.web.get_miner_configuration() return MinerConfig.from_boser(grpc_conf) async def set_power_limit(self, wattage: int) -> bool: try: - result = await self.web.grpc.set_power_target(wattage) + result = await self.web.set_power_target(wattage) except APIError: return False @@ -710,7 +709,7 @@ class BOSer(BaseMiner): async def _get_mac(self, grpc_miner_details: dict = None) -> Optional[str]: if grpc_miner_details is None: try: - grpc_miner_details = await self.web.grpc.get_miner_details() + grpc_miner_details = await self.web.get_miner_details() except APIError: pass @@ -740,7 +739,7 @@ class BOSer(BaseMiner): async def _get_fw_ver(self, grpc_miner_details: dict = None) -> Optional[str]: if grpc_miner_details is None: try: - grpc_miner_details = await self.web.grpc.get_miner_details() + grpc_miner_details = await self.web.get_miner_details() except APIError: pass @@ -763,7 +762,7 @@ class BOSer(BaseMiner): async def _get_hostname(self, grpc_miner_details: dict = None) -> Optional[str]: if grpc_miner_details is None: try: - grpc_miner_details = await self.web.grpc.get_miner_details() + grpc_miner_details = await self.web.get_miner_details() except APIError: pass @@ -791,7 +790,7 @@ class BOSer(BaseMiner): ) -> Optional[float]: if grpc_miner_details is None: try: - grpc_miner_details = await self.web.grpc.get_miner_details() + grpc_miner_details = await self.web.get_miner_details() except APIError: pass @@ -809,7 +808,7 @@ class BOSer(BaseMiner): if grpc_hashboards is None: try: - grpc_hashboards = await self.web.grpc.get_hashboards() + grpc_hashboards = await self.web.get_hashboards() except APIError: pass @@ -840,7 +839,7 @@ class BOSer(BaseMiner): async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]: if grpc_miner_stats is None: try: - grpc_miner_stats = self.web.grpc.get_miner_stats() + grpc_miner_stats = self.web.get_miner_stats() except APIError: pass @@ -855,9 +854,7 @@ class BOSer(BaseMiner): ) -> Optional[int]: if grpc_active_performance_mode is None: try: - grpc_active_performance_mode = ( - self.web.grpc.get_active_performance_mode() - ) + grpc_active_performance_mode = self.web.get_active_performance_mode() except APIError: pass @@ -872,7 +869,7 @@ class BOSer(BaseMiner): async def _get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]: if grpc_cooling_state is None: try: - grpc_cooling_state = self.web.grpc.get_cooling_state() + grpc_cooling_state = self.web.get_cooling_state() except APIError: pass @@ -922,9 +919,7 @@ class BOSer(BaseMiner): if grpc_locate_device_status is None: try: - grpc_locate_device_status = ( - await self.web.grpc.get_locate_device_status() - ) + grpc_locate_device_status = await self.web.get_locate_device_status() except APIError: pass diff --git a/pyasic/miners/base.py b/pyasic/miners/base.py index cd1027d9..8d2588f8 100644 --- a/pyasic/miners/base.py +++ b/pyasic/miners/base.py @@ -76,18 +76,12 @@ class GRPCCommand(WebAPICommand): cmd: str -@dataclass -class GraphQLCommand(WebAPICommand): - name: str - cmd: dict - - @dataclass class DataFunction: cmd: str - kwargs: List[ - Union[RPCAPICommand, WebAPICommand, GRPCCommand, GraphQLCommand] - ] = field(default_factory=list) + kwargs: List[Union[RPCAPICommand, WebAPICommand, GRPCCommand]] = field( + default_factory=list + ) def __call__(self, *args, **kwargs): return self diff --git a/pyasic/web/__init__.py b/pyasic/web/__init__.py index ecd836ad..6bdd3538 100644 --- a/pyasic/web/__init__.py +++ b/pyasic/web/__init__.py @@ -13,9 +13,11 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ +from __future__ import annotations + import warnings from abc import ABC, abstractmethod -from typing import Union +from typing import Any from pyasic.errors import APIWarning @@ -23,11 +25,13 @@ from pyasic.errors import APIWarning class BaseWebAPI(ABC): def __init__(self, ip: str) -> None: # ip address of the miner - self.ip = ip # ipaddress.ip_address(ip) - self.username = "root" + self.ip = ip + self.username = None self.pwd = None self.port = 80 + self.token = None + def __new__(cls, *args, **kwargs): if cls is BaseWebAPI: raise TypeError(f"Only children of '{cls.__name__}' may be instantiated") @@ -39,10 +43,11 @@ class BaseWebAPI(ABC): @abstractmethod async def send_command( self, - command: Union[str, bytes], + command: str | bytes, ignore_errors: bool = False, allow_warning: bool = True, - **parameters: Union[str, int, bool], + privileged: bool = False, + **parameters: Any, ) -> dict: pass diff --git a/pyasic/web/antminer.py b/pyasic/web/antminer.py index 944ad55a..b54627d8 100644 --- a/pyasic/web/antminer.py +++ b/pyasic/web/antminer.py @@ -13,9 +13,11 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ +from __future__ import annotations + import asyncio import json -from typing import Union +from typing import Any import httpx @@ -26,14 +28,16 @@ from pyasic.web import BaseWebAPI class AntminerModernWebAPI(BaseWebAPI): def __init__(self, ip: str) -> None: super().__init__(ip) + self.username = "root" self.pwd = settings.get("default_antminer_web_password", "root") async def send_command( self, - command: Union[str, bytes], + command: str | bytes, ignore_errors: bool = False, allow_warning: bool = True, - **parameters: Union[str, int, bool], + privileged: bool = False, + **parameters: Any, ) -> dict: url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi" auth = httpx.DigestAuth(self.username, self.pwd) @@ -44,9 +48,9 @@ class AntminerModernWebAPI(BaseWebAPI): if parameters: data = await client.post( url, - data=json.dumps(parameters), auth=auth, - timeout=settings.get("api_function_timeout", 3), # noqa + timeout=settings.get("api_function_timeout", 3), + json=parameters, ) else: data = await client.get(url, auth=auth) @@ -76,7 +80,9 @@ class AntminerModernWebAPI(BaseWebAPI): data["multicommand"] = True return data - async def _handle_multicommand(self, client: httpx.AsyncClient, command: str): + async def _handle_multicommand( + self, client: httpx.AsyncClient, command: str + ) -> dict: auth = httpx.DigestAuth(self.username, self.pwd) try: @@ -142,14 +148,16 @@ class AntminerModernWebAPI(BaseWebAPI): class AntminerOldWebAPI(BaseWebAPI): def __init__(self, ip: str) -> None: super().__init__(ip) + self.username = "root" self.pwd = settings.get("default_antminer_web_password", "root") async def send_command( self, - command: Union[str, bytes], + command: str | bytes, ignore_errors: bool = False, allow_warning: bool = True, - **parameters: Union[str, int, bool], + privileged: bool = False, + **parameters: Any, ) -> dict: url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi" auth = httpx.DigestAuth(self.username, self.pwd) diff --git a/pyasic/web/auradine.py b/pyasic/web/auradine.py index 621ae6d7..0ee09619 100644 --- a/pyasic/web/auradine.py +++ b/pyasic/web/auradine.py @@ -13,10 +13,12 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ +from __future__ import annotations + import asyncio import json import warnings -from typing import Any, List, Union +from typing import Any import httpx @@ -31,9 +33,9 @@ class FluxWebAPI(BaseWebAPI): self.username = "admin" self.pwd = settings.get("default_auradine_web_password", "admin") self.port = 8080 - self.jwt = None + self.token = None - async def auth(self): + async def auth(self) -> str | None: async with httpx.AsyncClient(transport=settings.transport()) as client: try: auth = await client.post( @@ -49,20 +51,24 @@ class FluxWebAPI(BaseWebAPI): else: json_auth = auth.json() try: - self.jwt = json_auth["Token"][0]["Token"] + self.token = json_auth["Token"][0]["Token"] except LookupError: return None - return self.jwt + return self.token async def send_command( self, - command: Union[str, bytes], - post: bool = False, + command: str | bytes, ignore_errors: bool = False, allow_warning: bool = True, + privileged: bool = False, **parameters: Any, ) -> dict: - if self.jwt is None: + post = privileged or not parameters == {} + if not parameters == {}: + parameters["command"] = command + + if self.token is None: await self.auth() async with httpx.AsyncClient(transport=settings.transport()) as client: for i in range(settings.get("get_data_retries", 1)): @@ -70,20 +76,14 @@ class FluxWebAPI(BaseWebAPI): if post: response = await client.post( f"http://{self.ip}:{self.port}/{command}", - headers={"Token": self.jwt}, + headers={"Token": self.token}, timeout=settings.get("api_function_timeout", 5), - ) - elif parameters: - response = await client.post( - f"http://{self.ip}:{self.port}/{command}", - headers={"Token": self.jwt}, - timeout=settings.get("api_function_timeout", 5), - json={"command": command, **parameters}, + json=parameters, ) else: response = await client.get( f"http://{self.ip}:{self.port}/{command}", - headers={"Token": self.jwt}, + headers={"Token": self.token}, timeout=settings.get("api_function_timeout", 5), ) json_data = response.json() @@ -95,9 +95,7 @@ class FluxWebAPI(BaseWebAPI): await self.auth() continue return json_data - except httpx.HTTPError: - pass - except json.JSONDecodeError: + except (httpx.HTTPError, json.JSONDecodeError): pass async def multicommand( @@ -166,97 +164,97 @@ class FluxWebAPI(BaseWebAPI): return False, data["STATUS"][0]["Msg"] return True, None - async def factory_reset(self): - return await self.send_command("factory-reset", post=True) + async def factory_reset(self) -> dict: + return await self.send_command("factory-reset", privileged=True) - async def get_fan(self): + async def get_fan(self) -> dict: return await self.send_command("fan") - async def set_fan(self, fan: int, speed_pct: int): + async def set_fan(self, fan: int, speed_pct: int) -> dict: return await self.send_command("fan", index=fan, percentage=speed_pct) - async def firmware_upgrade(self, url: str = None, version: str = "latest"): + async def firmware_upgrade(self, url: str = None, version: str = "latest") -> dict: if url is not None: return await self.send_command("firmware-upgrade", url=url) return await self.send_command("firmware-upgrade", version=version) - async def get_frequency(self): + async def get_frequency(self) -> dict: return await self.send_command("frequency") - async def set_frequency(self, board: int, frequency: float): + async def set_frequency(self, board: int, frequency: float) -> dict: return await self.send_command("frequency", board=board, frequency=frequency) - async def ipreport(self): + async def ipreport(self) -> dict: return await self.send_command("ipreport") - async def get_led(self): + async def get_led(self) -> dict: return await self.send_command("led") - async def set_led(self, code: int): + async def set_led(self, code: int) -> dict: return await self.send_command("led", code=code) - async def set_led_custom(self, code: int, led_1: int, led_2: int, msg: str): + async def set_led_custom(self, code: int, led_1: int, led_2: int, msg: str) -> dict: return await self.send_command( "led", code=code, led1=led_1, led2=led_2, msg=msg ) - async def get_mode(self): + async def get_mode(self) -> dict: return await self.send_command("mode") - async def set_mode(self, **kwargs): + async def set_mode(self, **kwargs) -> dict: return await self.send_command("mode", **kwargs) - async def get_network(self): + async def get_network(self) -> dict: return await self.send_command("network") - async def set_network(self, **kwargs): + async def set_network(self, **kwargs) -> dict: return await self.send_command("network", **kwargs) - async def password(self, password: str): + async def password(self, password: str) -> dict: res = await self.send_command( "password", user=self.username, old=self.pwd, new=password ) self.pwd = password return res - async def get_psu(self): + async def get_psu(self) -> dict: return await self.send_command("psu") - async def set_psu(self, voltage: float): + async def set_psu(self, voltage: float) -> dict: return await self.send_command("psu", voltage=voltage) - async def get_register(self): + async def get_register(self) -> dict: return await self.send_command("register") - async def set_register(self, company: str): + async def set_register(self, company: str) -> dict: return await self.send_command("register", parameter=company) - async def reboot(self): - return await self.send_command("restart", post=True) + async def reboot(self) -> dict: + return await self.send_command("restart", privileged=True) - async def restart_gcminer(self): + async def restart_gcminer(self) -> dict: return await self.send_command("restart", parameter="gcminer") - async def restart_api_server(self): + async def restart_api_server(self) -> dict: return await self.send_command("restart", parameter="api-server") - async def temperature(self): + async def temperature(self) -> dict: return await self.send_command("temperature") - async def timedate(self, ntp: str, timezone: str): + async def timedate(self, ntp: str, timezone: str) -> dict: return await self.send_command("timedate", ntp=ntp, timezone=timezone) - async def token(self): + async def token(self) -> dict: return await self.send_command("token", user=self.username, password=self.pwd) - async def update_pools(self, pools: List[dict]): + async def update_pools(self, pools: list[dict]) -> dict: return await self.send_command("updatepools", pools=pools) - async def voltage(self): + async def voltage(self) -> dict: return await self.send_command("voltage") - async def get_ztp(self): + async def get_ztp(self) -> dict: return await self.send_command("ztp") - async def set_ztp(self, enable: bool): + async def set_ztp(self, enable: bool) -> dict: return await self.send_command("ztp", parameter="on" if enable else "off") diff --git a/pyasic/web/braiins_os/__init__.py b/pyasic/web/braiins_os/__init__.py index 30737bfa..95ba7d52 100644 --- a/pyasic/web/braiins_os/__init__.py +++ b/pyasic/web/braiins_os/__init__.py @@ -1,149 +1,2 @@ -# ------------------------------------------------------------------------------ -# Copyright 2022 Upstream Data Inc - -# - -# Licensed under the Apache License, Version 2.0 (the "License"); - -# you may not use this file except in compliance with the License. - -# You may obtain a copy of the License at - -# - -# http://www.apache.org/licenses/LICENSE-2.0 - -# - -# Unless required by applicable law or agreed to in writing, software - -# distributed under the License is distributed on an "AS IS" BASIS, - -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -# See the License for the specific language governing permissions and - -# limitations under the License. - -# ------------------------------------------------------------------------------ -import asyncio -from typing import Union - -from pyasic import settings -from pyasic.errors import APIError -from pyasic.web import BaseWebAPI - -from .graphql import BOSerGraphQLAPI -from .grpc import BOSerGRPCAPI -from .luci import BOSMinerLuCIAPI - - -class BOSMinerWebAPI(BaseWebAPI): - def __init__(self, ip: str) -> None: - self.luci = BOSMinerLuCIAPI( - ip, settings.get("default_bosminer_password", "root") - ) - self._pwd = settings.get("default_bosminer_password", "root") - self._port = 80 - super().__init__(ip) - - @property - def pwd(self): - return self._pwd - - @pwd.setter - def pwd(self, other: str): - self._pwd = other - self.luci.pwd = other - - @property - def port(self): - return self._port - - @port.setter - def port(self, other: str): - self._port = other - self.luci.port = other - - async def send_command( - self, - command: Union[str, dict], - ignore_errors: bool = False, - allow_warning: bool = True, - **parameters: Union[str, int, bool], - ) -> dict: - return await self.luci.send_command(command) - - async def multicommand( - self, *commands: Union[dict, str], allow_warning: bool = True - ) -> dict: - return await self.luci.multicommand(*commands) - - -class BOSerWebAPI(BOSMinerWebAPI): - def __init__(self, ip: str) -> None: - self.gql = BOSerGraphQLAPI( - ip, settings.get("default_bosminer_password", "root") - ) - self.grpc = BOSerGRPCAPI(ip, settings.get("default_bosminer_password", "root")) - self._port = 80 - super().__init__(ip) - - @property - def pwd(self): - return self._pwd - - @pwd.setter - def pwd(self, other: str): - self._pwd = other - self.luci.pwd = other - self.gql.pwd = other - self.grpc.pwd = other - - @property - def port(self): - return self._port - - @port.setter - def port(self, other: str): - self._port = other - self.luci.port = other - self.gql.port = other - - async def send_command( - self, - command: Union[str, dict], - ignore_errors: bool = False, - allow_warning: bool = True, - **parameters: Union[str, int, bool], - ) -> dict: - command_type = self.select_command_type(command) - if command_type == "gql": - return await self.gql.send_command(command) - elif command_type == "grpc": - try: - return await getattr(self.grpc, command.replace("grpc_", ""))() - except AttributeError: - raise APIError(f"No gRPC command found for command: {command}") - elif command_type == "luci": - return await self.luci.send_command(command) - - @staticmethod - def select_command_type(command: Union[str, dict]) -> str: - if isinstance(command, dict): - return "gql" - else: - return "grpc" - - async def multicommand( - self, *commands: Union[dict, str], allow_warning: bool = True - ) -> dict: - cmd_types = {"grpc": [], "gql": []} - for cmd in commands: - cmd_types[self.select_command_type(cmd)].append(cmd) - - async def no_op(): - return {} - - if len(cmd_types["grpc"]) > 0: - grpc_data_t = asyncio.create_task( - self.grpc.multicommand(*cmd_types["grpc"]) - ) - else: - grpc_data_t = asyncio.create_task(no_op()) - if len(cmd_types["gql"]) > 0: - gql_data_t = asyncio.create_task(self.gql.multicommand(*cmd_types["gql"])) - else: - gql_data_t = asyncio.create_task(no_op()) - - await asyncio.gather(grpc_data_t, gql_data_t) - - data = dict(**grpc_data_t.result(), **gql_data_t.result()) - return data +from .boser import BOSerWebAPI +from .bosminer import BOSMinerWebAPI diff --git a/pyasic/web/braiins_os/grpc.py b/pyasic/web/braiins_os/boser.py similarity index 71% rename from pyasic/web/braiins_os/grpc.py rename to pyasic/web/braiins_os/boser.py index c5f405e9..3ff09a0b 100644 --- a/pyasic/web/braiins_os/grpc.py +++ b/pyasic/web/braiins_os/boser.py @@ -13,15 +13,19 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ +from __future__ import annotations + import asyncio import logging from datetime import timedelta +from typing import Any -from betterproto import Message from grpclib import GRPCError, Status from grpclib.client import Channel +from pyasic import settings from pyasic.errors import APIError +from pyasic.web import BaseWebAPI from .proto.braiins.bos import * from .proto.braiins.bos.v1 import * @@ -41,14 +45,13 @@ class BOSMinerGRPCStub( pass -class BOSerGRPCAPI: - def __init__(self, ip: str, pwd: str): - self.ip = ip +class BOSerWebAPI(BaseWebAPI): + def __init__(self, ip: str) -> None: + super().__init__(ip) self.username = "root" - self.pwd = pwd + self.pwd = settings.get("default_bosminer_password", "root") self.port = 50051 - self._auth = None - self._auth_time = datetime.now() + self._auth_time = None @property def commands(self) -> list: @@ -85,13 +88,15 @@ class BOSerGRPCAPI: async def send_command( self, - command: str, - message: Message = None, + command: str | bytes, ignore_errors: bool = False, - auth: bool = True, + allow_warning: bool = True, + privileged: bool = False, + **parameters: Any, ) -> dict: + message: betterproto.Message = parameters["message"] metadata = [] - if auth: + if privileged: metadata.append(("authorization", await self.auth())) try: async with Channel(self.ip, self.port) as c: @@ -111,15 +116,15 @@ class BOSerGRPCAPI: except GRPCError as e: raise APIError(f"gRPC command failed - {endpoint}") from e - async def auth(self): - if self._auth is not None and self._auth_time - datetime.now() < timedelta( + async def auth(self) -> str | None: + if self.token is not None and self._auth_time - datetime.now() < timedelta( seconds=3540 ): - return self._auth + return self.token await self._get_auth() - return self._auth + return self.token - async def _get_auth(self): + async def _get_auth(self) -> str: async with Channel(self.ip, self.port) as c: req = LoginRequest(username=self.username, password=self.pwd) async with c.request( @@ -132,74 +137,79 @@ class BOSerGRPCAPI: await stream.recv_initial_metadata() auth = stream.initial_metadata.get("authorization") if auth is not None: - self._auth = auth + self.token = auth self._auth_time = datetime.now() - return self._auth + return self.token - async def get_api_version(self): + async def get_api_version(self) -> dict: return await self.send_command( - "get_api_version", ApiVersionRequest(), auth=False + "get_api_version", message=ApiVersionRequest(), privileged=False ) - async def start(self): - return await self.send_command("start", StartRequest()) + async def start(self) -> dict: + return await self.send_command("start", message=StartRequest()) - async def stop(self): - return await self.send_command("stop", StopRequest()) + async def stop(self) -> dict: + return await self.send_command("stop", message=StopRequest()) - async def pause_mining(self): - return await self.send_command("pause_mining", PauseMiningRequest()) + async def pause_mining(self) -> dict: + return await self.send_command("pause_mining", message=PauseMiningRequest()) - async def resume_mining(self): - return await self.send_command("resume_mining", ResumeMiningRequest()) + async def resume_mining(self) -> dict: + return await self.send_command("resume_mining", message=ResumeMiningRequest()) - async def restart(self): - return await self.send_command("restart", RestartRequest()) + async def restart(self) -> dict: + return await self.send_command("restart", message=RestartRequest()) - async def reboot(self): - return await self.send_command("reboot", RebootRequest()) + async def reboot(self) -> dict: + return await self.send_command("reboot", message=RebootRequest()) - async def set_locate_device_status(self, enable: bool): + async def set_locate_device_status(self, enable: bool) -> dict: return await self.send_command( - "set_locate_device_status", SetLocateDeviceStatusRequest(enable=enable) + "set_locate_device_status", + message=SetLocateDeviceStatusRequest(enable=enable), ) - async def get_locate_device_status(self): + async def get_locate_device_status(self) -> dict: return await self.send_command( - "get_locate_device_status", GetLocateDeviceStatusRequest() + "get_locate_device_status", message=GetLocateDeviceStatusRequest() ) - async def set_password(self, password: str = None): + async def set_password(self, password: str = None) -> dict: return await self.send_command( - "set_password", SetPasswordRequest(password=password) + "set_password", message=SetPasswordRequest(password=password) ) - async def get_cooling_state(self): - return await self.send_command("get_cooling_state", GetCoolingStateRequest()) + async def get_cooling_state(self) -> dict: + return await self.send_command( + "get_cooling_state", message=GetCoolingStateRequest() + ) async def set_immersion_mode( self, enable: bool, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, - ): + ) -> dict: return await self.send_command( "set_immersion_mode", - SetImmersionModeRequest( + message=SetImmersionModeRequest( enable_immersion_mode=enable, save_action=save_action ), ) - async def get_tuner_state(self): - return await self.send_command("get_tuner_state", GetTunerStateRequest()) - - async def list_target_profiles(self): + async def get_tuner_state(self) -> dict: return await self.send_command( - "list_target_profiles", ListTargetProfilesRequest() + "get_tuner_state", message=GetTunerStateRequest() + ) + + async def list_target_profiles(self) -> dict: + return await self.send_command( + "list_target_profiles", message=ListTargetProfilesRequest() ) async def set_default_power_target( self, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY - ): + ) -> dict: return await self.send_command( "set_default_power_target", message=SetDefaultPowerTargetRequest(save_action=save_action), @@ -209,10 +219,10 @@ class BOSerGRPCAPI: self, power_target: int, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, - ): + ) -> dict: return await self.send_command( "set_power_target", - SetPowerTargetRequest( + message=SetPowerTargetRequest( power_target=Power(watt=power_target), save_action=save_action ), ) @@ -221,7 +231,7 @@ class BOSerGRPCAPI: self, power_target_increment: int, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, - ): + ) -> dict: return await self.send_command( "increment_power_target", message=IncrementPowerTargetRequest( @@ -234,7 +244,7 @@ class BOSerGRPCAPI: self, power_target_decrement: int, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, - ): + ) -> dict: return await self.send_command( "decrement_power_target", message=DecrementPowerTargetRequest( @@ -245,7 +255,7 @@ class BOSerGRPCAPI: async def set_default_hashrate_target( self, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY - ): + ) -> dict: return await self.send_command( "set_default_hashrate_target", message=SetDefaultHashrateTargetRequest(save_action=save_action), @@ -255,10 +265,10 @@ class BOSerGRPCAPI: self, hashrate_target: float, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, - ): + ) -> dict: return await self.send_command( "set_hashrate_target", - SetHashrateTargetRequest( + message=SetHashrateTargetRequest( hashrate_target=TeraHashrate(terahash_per_second=hashrate_target), save_action=save_action, ), @@ -268,10 +278,10 @@ class BOSerGRPCAPI: self, hashrate_target_increment: int, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, - ): + ) -> dict: return await self.send_command( "increment_hashrate_target", - IncrementHashrateTargetRequest( + message=IncrementHashrateTargetRequest( hashrate_target_increment=TeraHashrate( terahash_per_second=hashrate_target_increment ), @@ -283,10 +293,10 @@ class BOSerGRPCAPI: self, hashrate_target_decrement: int, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, - ): + ) -> dict: return await self.send_command( "decrement_hashrate_target", - DecrementHashrateTargetRequest( + message=DecrementHashrateTargetRequest( hashrate_target_decrement=TeraHashrate( terahash_per_second=hashrate_target_decrement ), @@ -301,7 +311,7 @@ class BOSerGRPCAPI: min_power_target: int, enable_shutdown: bool = None, shutdown_duration: int = None, - ): + ) -> dict: return await self.send_command( "set_dps", message=SetDpsRequest( @@ -322,7 +332,7 @@ class BOSerGRPCAPI: wattage_target: int = None, hashrate_target: int = None, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, - ): + ) -> dict: if wattage_target is not None and hashrate_target is not None: logging.error( "Cannot use both wattage_target and hashrate_target, using wattage_target." @@ -362,62 +372,71 @@ class BOSerGRPCAPI: ), ) - async def get_active_performance_mode(self): + async def get_active_performance_mode(self) -> dict: return await self.send_command( - "get_active_performance_mode", GetPerformanceModeRequest() + "get_active_performance_mode", message=GetPerformanceModeRequest() ) - async def get_pool_groups(self): - return await self.send_command("get_pool_groups", GetPoolGroupsRequest()) - - async def create_pool_group(self): - raise NotImplementedError - return await self.send_command("braiins.bos.v1.PoolService/CreatePoolGroup") - - async def update_pool_group(self): - raise NotImplementedError - return await self.send_command("braiins.bos.v1.PoolService/UpdatePoolGroup") - - async def remove_pool_group(self): - raise NotImplementedError - return await self.send_command("braiins.bos.v1.PoolService/RemovePoolGroup") - - async def get_miner_configuration(self): + async def get_pool_groups(self) -> dict: return await self.send_command( - "get_miner_configuration", GetMinerConfigurationRequest() + "get_pool_groups", message=GetPoolGroupsRequest() ) - async def get_constraints(self): - return await self.send_command("get_constraints", GetConstraintsRequest()) + async def create_pool_group(self) -> dict: + raise NotImplementedError - async def get_license_state(self): - return await self.send_command("get_license_state", GetLicenseStateRequest()) + async def update_pool_group(self) -> dict: + raise NotImplementedError - async def get_miner_status(self): - return await self.send_command("get_miner_status", GetMinerStatusRequest()) + async def remove_pool_group(self) -> dict: + raise NotImplementedError - async def get_miner_details(self): - return await self.send_command("get_miner_details", GetMinerDetailsRequest()) - - async def get_miner_stats(self): - return await self.send_command("get_miner_stats", GetMinerStatsRequest()) - - async def get_hashboards(self): - return await self.send_command("get_hashboards", GetHashboardsRequest()) - - async def get_support_archive(self): + async def get_miner_configuration(self) -> dict: return await self.send_command( - "get_support_archive", GetSupportArchiveRequest() + "get_miner_configuration", message=GetMinerConfigurationRequest() + ) + + async def get_constraints(self) -> dict: + return await self.send_command( + "get_constraints", message=GetConstraintsRequest() + ) + + async def get_license_state(self) -> dict: + return await self.send_command( + "get_license_state", message=GetLicenseStateRequest() + ) + + async def get_miner_status(self) -> dict: + return await self.send_command( + "get_miner_status", message=GetMinerStatusRequest() + ) + + async def get_miner_details(self) -> dict: + return await self.send_command( + "get_miner_details", message=GetMinerDetailsRequest() + ) + + async def get_miner_stats(self) -> dict: + return await self.send_command( + "get_miner_stats", message=GetMinerStatsRequest() + ) + + async def get_hashboards(self) -> dict: + return await self.send_command("get_hashboards", message=GetHashboardsRequest()) + + async def get_support_archive(self) -> dict: + return await self.send_command( + "get_support_archive", message=GetSupportArchiveRequest() ) async def enable_hashboards( self, hashboard_ids: List[str], save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, - ): + ) -> dict: return await self.send_command( "enable_hashboards", - EnableHashboardsRequest( + message=EnableHashboardsRequest( hashboard_ids=hashboard_ids, save_action=save_action ), ) @@ -426,10 +445,10 @@ class BOSerGRPCAPI: self, hashboard_ids: List[str], save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, - ): + ) -> dict: return await self.send_command( "disable_hashboards", - DisableHashboardsRequest( + message=DisableHashboardsRequest( hashboard_ids=hashboard_ids, save_action=save_action ), ) diff --git a/pyasic/web/braiins_os/luci.py b/pyasic/web/braiins_os/bosminer.py similarity index 75% rename from pyasic/web/braiins_os/luci.py rename to pyasic/web/braiins_os/bosminer.py index 24aa7a5e..59b9e520 100644 --- a/pyasic/web/braiins_os/luci.py +++ b/pyasic/web/braiins_os/bosminer.py @@ -13,33 +13,38 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ +from __future__ import annotations + import json +from typing import Any import httpx from pyasic import settings from pyasic.errors import APIError +from pyasic.web import BaseWebAPI -class BOSMinerLuCIAPI: - def __init__(self, ip: str, pwd: str): - self.ip = ip +class BOSMinerWebAPI(BaseWebAPI): + def __init__(self, ip: str) -> None: + super().__init__(ip) self.username = "root" - self.pwd = pwd + self.pwd = settings.get("default_bosminer_password", "root") self.port = 80 - async def multicommand(self, *commands: str) -> dict: - data = {} - for command in commands: - data[command] = await self.send_command(command, ignore_errors=True) - return data - - async def send_command(self, path: str, ignore_errors: bool = False) -> dict: + async def send_command( + self, + command: str | bytes, + ignore_errors: bool = False, + allow_warning: bool = True, + privileged: bool = False, + **parameters: Any, + ) -> dict: try: async with httpx.AsyncClient(transport=settings.transport()) as client: await self.auth(client) data = await client.get( - f"http://{self.ip}:{self.port}/cgi-bin/luci/{path}", + f"http://{self.ip}:{self.port}/cgi-bin/luci/{command}", headers={"User-Agent": "BTC Tools v0.1"}, ) if data.status_code == 200: @@ -47,14 +52,20 @@ class BOSMinerLuCIAPI: if ignore_errors: return {} raise APIError( - f"LUCI web command failed: path={path}, code={data.status_code}" + f"LUCI web command failed: command={command}, code={data.status_code}" ) except (httpx.HTTPError, json.JSONDecodeError): if ignore_errors: return {} - raise APIError(f"LUCI web command failed: path={path}") + raise APIError(f"LUCI web command failed: command={command}") - async def auth(self, session: httpx.AsyncClient): + async def multicommand(self, *commands: str) -> dict: + data = {} + for command in commands: + data[command] = await self.send_command(command, ignore_errors=True) + return data + + async def auth(self, session: httpx.AsyncClient) -> None: login = {"luci_username": self.username, "luci_password": self.pwd} url = f"http://{self.ip}:{self.port}/cgi-bin/luci" headers = { @@ -63,22 +74,22 @@ class BOSMinerLuCIAPI: } await session.post(url, headers=headers, data=login) - async def get_net_conf(self): + async def get_net_conf(self) -> dict: return await self.send_command("admin/network/iface_status/lan") - async def get_cfg_metadata(self): + async def get_cfg_metadata(self) -> dict: return await self.send_command("admin/miner/cfg_metadata") - async def get_cfg_data(self): + async def get_cfg_data(self) -> dict: return await self.send_command("admin/miner/cfg_data") - async def get_bos_info(self): + async def get_bos_info(self) -> dict: return await self.send_command("bos/info") - async def get_overview(self): + async def get_overview(self) -> dict: return await self.send_command( "admin/status/overview?status=1" ) # needs status=1 or it fails - async def get_api_status(self): + async def get_api_status(self) -> dict: return await self.send_command("admin/miner/api_status") diff --git a/pyasic/web/braiins_os/graphql.py b/pyasic/web/braiins_os/graphql.py deleted file mode 100644 index b70cb287..00000000 --- a/pyasic/web/braiins_os/graphql.py +++ /dev/null @@ -1,105 +0,0 @@ -# ------------------------------------------------------------------------------ -# Copyright 2022 Upstream Data Inc - -# - -# Licensed under the Apache License, Version 2.0 (the "License"); - -# you may not use this file except in compliance with the License. - -# You may obtain a copy of the License at - -# - -# http://www.apache.org/licenses/LICENSE-2.0 - -# - -# Unless required by applicable law or agreed to in writing, software - -# distributed under the License is distributed on an "AS IS" BASIS, - -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -# See the License for the specific language governing permissions and - -# limitations under the License. - -# ------------------------------------------------------------------------------ -import json -from typing import Union - -import httpx - -from pyasic import settings - - -class BOSerGraphQLAPI: - def __init__(self, ip: str, pwd: str): - self.ip = ip - self.username = "root" - self.pwd = pwd - self.port = 80 - - async def multicommand(self, *commands: dict) -> dict: - def merge(*d: dict): - ret = {} - for i in d: - if i: - for k in i: - if k not in ret: - ret[k] = i[k] - else: - ret[k] = merge(ret[k], i[k]) - return None if ret == {} else ret - - command = merge(*commands) - data = await self.send_command(command) - if data is not None: - if data.get("data") is None: - try: - commands = list(commands) - # noinspection PyTypeChecker - commands.remove({"bos": {"faultLight": None}}) - command = merge(*commands) - data = await self.send_command(command) - except (LookupError, ValueError): - pass - if not data: - data = {} - data["multicommand"] = False - return data - - async def send_command( - self, - command: dict, - ) -> dict: - url = f"http://{self.ip}:{self.port}/graphql" - query = command - if command is None: - return {} - if command.get("query") is None: - query = {"query": self.parse_command(command)} - try: - async with httpx.AsyncClient(transport=settings.transport()) as client: - await self.auth(client) - data = await client.post(url, json=query) - except httpx.HTTPError: - pass - else: - if data.status_code == 200: - try: - return data.json() - except json.decoder.JSONDecodeError: - pass - - def parse_command(self, graphql_command: Union[dict, set]) -> str: - if isinstance(graphql_command, dict): - data = [] - for key in graphql_command: - if graphql_command[key] is not None: - parsed = self.parse_command(graphql_command[key]) - data.append(key + parsed) - else: - data.append(key) - else: - data = graphql_command - return "{" + ",".join(data) + "}" - - async def auth(self, client: httpx.AsyncClient) -> None: - url = f"http://{self.ip}:{self.port}/graphql" - await client.post( - url, - json={ - "query": ( - f'mutation{{auth{{login(username:"{self.username}", password:"{self.pwd}"){{__typename}}}}}}' - ) - }, - ) diff --git a/pyasic/web/epic.py b/pyasic/web/epic.py index 8e4811e4..b4bf5071 100644 --- a/pyasic/web/epic.py +++ b/pyasic/web/epic.py @@ -13,8 +13,10 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ +from __future__ import annotations + import json -from typing import Union +from typing import Any import httpx @@ -28,32 +30,30 @@ class ePICWebAPI(BaseWebAPI): super().__init__(ip) self.username = "root" self.pwd = settings.get("default_epic_web_password", "letmein") - self.token = None self.port = 4028 + self.token = None async def send_command( self, - command: Union[str, bytes], + command: str | bytes, ignore_errors: bool = False, allow_warning: bool = True, - post: bool = False, - **parameters: Union[str, int, bool], + privileged: bool = False, + **parameters: Any, ) -> dict: - if post or parameters != {}: - post = True + post = privileged or not parameters == {} async with httpx.AsyncClient(transport=settings.transport()) as client: for i in range(settings.get("get_data_retries", 1) + 1): try: if post: - epic_param = { - "param": parameters.get("parameters"), - "password": self.pwd, - } response = await client.post( f"http://{self.ip}:{self.port}/{command}", timeout=5, - json=epic_param, + json={ + **parameters, + "password": self.pwd, + }, ) else: response = await client.get( @@ -89,31 +89,31 @@ class ePICWebAPI(BaseWebAPI): return data async def restart_epic(self) -> dict: - return await self.send_command("softreboot", post=True) + return await self.send_command("softreboot", privileged=True) async def reboot(self) -> dict: - return await self.send_command("reboot", post=True) + return await self.send_command("reboot", privileged=True) async def pause_mining(self) -> dict: - return await self.send_command("miner", post=True, parameters="Stop") + return await self.send_command("miner", param="Stop") async def resume_mining(self) -> dict: - return await self.send_command("miner", post=True, parameters="Autostart") + return await self.send_command("miner", param="Autostart") async def stop_mining(self) -> dict: - return await self.send_command("miner", post=True, parameters="Stop") + return await self.send_command("miner", param="Stop") async def start_mining(self) -> dict: - return await self.send_command("miner", post=True, parameters="Autostart") + return await self.send_command("miner", param="Autostart") - async def summary(self): + async def summary(self) -> dict: return await self.send_command("summary") - async def hashrate(self): + async def hashrate(self) -> dict: return await self.send_command("hashrate") - async def network(self): + async def network(self) -> dict: return await self.send_command("network") - async def capabilities(self): + async def capabilities(self) -> dict: return await self.send_command("capabilities") diff --git a/pyasic/web/goldshell.py b/pyasic/web/goldshell.py index 10072bed..637c8f8c 100644 --- a/pyasic/web/goldshell.py +++ b/pyasic/web/goldshell.py @@ -13,9 +13,11 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ +from __future__ import annotations + import json import warnings -from typing import Union +from typing import Any import httpx @@ -28,9 +30,9 @@ class GoldshellWebAPI(BaseWebAPI): super().__init__(ip) self.username = "admin" self.pwd = settings.get("default_goldshell_web_password", "123456789") - self.jwt = None + self.token = None - async def auth(self): + async def auth(self) -> str | None: async with httpx.AsyncClient(transport=settings.transport()) as client: try: await client.get(f"http://{self.ip}:{self.port}/user/logout") @@ -54,47 +56,43 @@ class GoldshellWebAPI(BaseWebAPI): f"Could not authenticate web token with miner: {self}" ) else: - self.jwt = auth.get("JWT Token") + self.token = auth.get("JWT Token") else: - self.jwt = auth.get("JWT Token") - return self.jwt + self.token = auth.get("JWT Token") + return self.token async def send_command( self, - command: Union[str, bytes], + command: str | bytes, ignore_errors: bool = False, allow_warning: bool = True, - **parameters: Union[str, int, bool], + privileged: bool = False, + **parameters: Any, ) -> dict: - if parameters.get("pool_pwd"): - parameters["pass"] = parameters["pool_pwd"] - parameters.pop("pool_pwd") - if self.jwt is None: + if self.token is None: await self.auth() async with httpx.AsyncClient(transport=settings.transport()) as client: for _ in range(settings.get("get_data_retries", 1)): try: - if parameters: + if not parameters == {}: response = await client.put( f"http://{self.ip}:{self.port}/mcb/{command}", - headers={"Authorization": "Bearer " + self.jwt}, + headers={"Authorization": "Bearer " + self.token}, timeout=settings.get("api_function_timeout", 5), json=parameters, ) else: response = await client.get( f"http://{self.ip}:{self.port}/mcb/{command}", - headers={"Authorization": "Bearer " + self.jwt}, + headers={"Authorization": "Bearer " + self.token}, timeout=settings.get("api_function_timeout", 5), ) json_data = response.json() return json_data - except httpx.HTTPError: - pass - except json.JSONDecodeError: - pass except TypeError: await self.auth() + except (httpx.HTTPError, json.JSONDecodeError): + pass async def multicommand( self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True @@ -107,7 +105,7 @@ class GoldshellWebAPI(BaseWebAPI): try: response = await client.get( f"http://{self.ip}:{self.port}/mcb/{command}", - headers={"Authorization": "Bearer " + self.jwt}, + headers={"Authorization": "Bearer " + self.token}, timeout=settings.get("api_function_timeout", 5), ) json_data = response.json() @@ -120,19 +118,25 @@ class GoldshellWebAPI(BaseWebAPI): await self.auth() return data - async def pools(self): + async def pools(self) -> dict: return await self.send_command("pools") - async def newpool(self, url: str, user: str, password: str): - return await self.send_command("newpool", url=url, user=user, pool_pwd=password) - - async def delpool(self, url: str, user: str, password: str, dragid: int = 0): + async def newpool(self, url: str, user: str, password: str) -> dict: + # looks dumb, but cant pass `pass` since it is a built in type return await self.send_command( - "delpool", url=url, user=user, pool_pwd=password, dragid=dragid + "newpool", **{"url": url, "user": user, "pass": password} ) - async def setting(self): + async def delpool( + self, url: str, user: str, password: str, dragid: int = 0 + ) -> dict: + # looks dumb, but cant pass `pass` since it is a built in type + return await self.send_command( + "delpool", **{"url": url, "user": user, "pass": password, "dragid": dragid} + ) + + async def setting(self) -> dict: return await self.send_command("setting") - async def status(self): + async def status(self) -> dict: return await self.send_command("status") diff --git a/pyasic/web/innosilicon.py b/pyasic/web/innosilicon.py index 4a842a89..db3d7fbf 100644 --- a/pyasic/web/innosilicon.py +++ b/pyasic/web/innosilicon.py @@ -13,9 +13,11 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ +from __future__ import annotations + import json import warnings -from typing import Union +from typing import Any import httpx @@ -29,9 +31,9 @@ class InnosiliconWebAPI(BaseWebAPI): super().__init__(ip) self.username = "admin" self.pwd = settings.get("default_innosilicon_web_password", "admin") - self.jwt = None + self.token = None - async def auth(self): + async def auth(self) -> str | None: async with httpx.AsyncClient(transport=settings.transport()) as client: try: auth = await client.post( @@ -42,24 +44,25 @@ class InnosiliconWebAPI(BaseWebAPI): warnings.warn(f"Could not authenticate web token with miner: {self}") else: json_auth = auth.json() - self.jwt = json_auth.get("jwt") - return self.jwt + self.token = json_auth.get("jwt") + return self.token async def send_command( self, - command: Union[str, bytes], + command: str | bytes, ignore_errors: bool = False, allow_warning: bool = True, - **parameters: Union[str, int, bool], + privileged: bool = False, + **parameters: Any, ) -> dict: - if self.jwt is None: + if self.token is None: await self.auth() async with httpx.AsyncClient(transport=settings.transport()) as client: for _ in range(settings.get("get_data_retries", 1)): try: response = await client.post( f"http://{self.ip}:{self.port}/api/{command}", - headers={"Authorization": "Bearer " + self.jwt}, + headers={"Authorization": "Bearer " + self.token}, timeout=settings.get("api_function_timeout", 5), json=parameters, ) @@ -79,9 +82,7 @@ class InnosiliconWebAPI(BaseWebAPI): raise APIError(json_data["message"]) raise APIError("Innosilicon web api command failed.") return json_data - except httpx.HTTPError: - pass - except json.JSONDecodeError: + except (httpx.HTTPError, json.JSONDecodeError): pass async def multicommand( @@ -95,7 +96,7 @@ class InnosiliconWebAPI(BaseWebAPI): try: response = await client.post( f"http://{self.ip}:{self.port}/api/{command}", - headers={"Authorization": "Bearer " + self.jwt}, + headers={"Authorization": "Bearer " + self.token}, timeout=settings.get("api_function_timeout", 5), ) json_data = response.json() @@ -123,14 +124,14 @@ class InnosiliconWebAPI(BaseWebAPI): async def type(self) -> dict: return await self.send_command("type") - async def get_all(self): + async def get_all(self) -> dict: return await self.send_command("getAll") - async def get_error_detail(self): + async def get_error_detail(self) -> dict: return await self.send_command("getErrorDetail") - async def pools(self): + async def pools(self) -> dict: return await self.send_command("pools") - async def poweroff(self): + async def poweroff(self) -> dict: return await self.send_command("poweroff") diff --git a/pyasic/web/vnish.py b/pyasic/web/vnish.py index cc92bbd4..b5ea2753 100644 --- a/pyasic/web/vnish.py +++ b/pyasic/web/vnish.py @@ -13,9 +13,11 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ +from __future__ import annotations + import json import warnings -from typing import Union +from typing import Any import httpx @@ -30,7 +32,7 @@ class VNishWebAPI(BaseWebAPI): self.pwd = settings.get("default_vnish_web_password", "admin") self.token = None - async def auth(self): + async def auth(self) -> str | None: async with httpx.AsyncClient(transport=settings.transport()) as client: try: auth = await client.post( @@ -51,11 +53,13 @@ class VNishWebAPI(BaseWebAPI): async def send_command( self, - command: Union[str, bytes], + command: str | bytes, ignore_errors: bool = False, allow_warning: bool = True, - **parameters: Union[str, int, bool], + privileged: bool = False, + **parameters: Any, ) -> dict: + post = privileged or not parameters == {} if self.token is None: await self.auth() async with httpx.AsyncClient(transport=settings.transport()) as client: @@ -65,15 +69,7 @@ class VNishWebAPI(BaseWebAPI): if command.startswith("system"): auth = "Bearer " + self.token - if parameters.get("post"): - parameters.pop("post") - response = await client.post( - f"http://{self.ip}:{self.port}/api/v1/{command}", - headers={"Authorization": auth}, - timeout=settings.get("api_function_timeout", 5), - json=parameters, - ) - elif not parameters == {}: + if post: response = await client.post( f"http://{self.ip}:{self.port}/api/v1/{command}", headers={"Authorization": auth}, @@ -94,11 +90,7 @@ class VNishWebAPI(BaseWebAPI): if json_data: return json_data return {"success": True} - except httpx.HTTPError: - pass - except json.JSONDecodeError: - pass - except AttributeError: + except (httpx.HTTPError, json.JSONDecodeError, AttributeError): pass async def multicommand( @@ -111,40 +103,40 @@ class VNishWebAPI(BaseWebAPI): return data async def restart_vnish(self) -> dict: - return await self.send_command("mining/restart", post=True) + return await self.send_command("mining/restart", privileged=True) async def reboot(self) -> dict: - return await self.send_command("system/reboot", post=True) + return await self.send_command("system/reboot", privileged=True) async def pause_mining(self) -> dict: - return await self.send_command("mining/pause", post=True) + return await self.send_command("mining/pause", privileged=True) async def resume_mining(self) -> dict: - return await self.send_command("mining/resume", post=True) + return await self.send_command("mining/resume", privileged=True) async def stop_mining(self) -> dict: - return await self.send_command("mining/stop", post=True) + return await self.send_command("mining/stop", privileged=True) async def start_mining(self) -> dict: - return await self.send_command("mining/start", post=True) + return await self.send_command("mining/start", privileged=True) - async def info(self): + async def info(self) -> dict: return await self.send_command("info") - async def summary(self): + async def summary(self) -> dict: return await self.send_command("summary") - async def chips(self): + async def chips(self) -> dict: return await self.send_command("chips") - async def layout(self): + async def layout(self) -> dict: return await self.send_command("layout") - async def status(self): + async def status(self) -> dict: return await self.send_command("status") - async def settings(self): + async def settings(self) -> dict: return await self.send_command("settings") - async def autotune_presets(self): + async def autotune_presets(self) -> dict: return await self.send_command("autotune/presets")