diff --git a/pyasic/miners/miner_factory.py b/pyasic/miners/miner_factory.py index ed5ad70b..6cf5542e 100644 --- a/pyasic/miners/miner_factory.py +++ b/pyasic/miners/miner_factory.py @@ -13,8 +13,6 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ - - import asyncio import enum import ipaddress @@ -25,6 +23,7 @@ from typing import AsyncGenerator, Callable, List, Optional, Tuple, Union import anyio import httpx +from pyasic import settings from pyasic.logger import logger from pyasic.miners.antminer import * from pyasic.miners.avalonminer import * @@ -45,8 +44,6 @@ from pyasic.miners.innosilicon import * from pyasic.miners.unknown import UnknownMiner from pyasic.miners.whatsminer import * -from pyasic import settings - class MinerTypes(enum.Enum): ANTMINER = 0 @@ -479,11 +476,15 @@ class MinerFactory: async def _get_miner_web(self, ip: str): urls = [f"http://{ip}/", f"https://{ip}/"] - async with httpx.AsyncClient(verify=False) as session: + async with httpx.AsyncClient( + transport=settings.transport(verify=False) + ) as session: tasks = [asyncio.create_task(self._web_ping(session, url)) for url in urls] text, resp = await concurrent_get_first_result( - tasks, lambda x: x[0] is not None and self._parse_web_type(x[0], x[1]) is not None + tasks, + lambda x: x[0] is not None + and self._parse_web_type(x[0], x[1]) is not None, ) if text is not None: return self._parse_web_type(text, resp) @@ -612,7 +613,7 @@ class MinerFactory: location: str, auth: Optional[httpx.DigestAuth] = None, ) -> Optional[dict]: - async with httpx.AsyncClient(verify=settings.ssl_cxt) as session: + async with httpx.AsyncClient(transport=settings.transport()) as session: try: data = await session.get( f"http://{str(ip)}{location}", @@ -808,7 +809,7 @@ class MinerFactory: async def get_miner_model_innosilicon(self, ip: str) -> Optional[str]: try: - async with httpx.AsyncClient(verify=settings.ssl_cxt) as session: + async with httpx.AsyncClient(transport=settings.transport()) as session: auth_req = await session.post( f"http://{ip}/api/auth", data={"username": "admin", "password": "admin"}, @@ -838,7 +839,7 @@ class MinerFactory: pass try: - async with httpx.AsyncClient(verify=settings.ssl_cxt) as session: + async with httpx.AsyncClient(transport=settings.transport()) as session: d = await session.post( f"http://{ip}/graphql", json={"query": "{bosminer {info{modelName}}}"}, diff --git a/pyasic/network/__init__.py b/pyasic/network/__init__.py index b82cd8a6..3e626765 100644 --- a/pyasic/network/__init__.py +++ b/pyasic/network/__init__.py @@ -45,7 +45,7 @@ class MinerNetwork: """ hosts = [] for address in addresses: - hosts = [*hosts, *cls.from_address(address)] + hosts = [*hosts, *cls.from_address(address).hosts] return cls(sorted(list(set(hosts)))) @classmethod @@ -63,7 +63,9 @@ class MinerNetwork: return cls.from_octets(*octets) @classmethod - def from_octets(cls, oct_1: str, oct_2: str, oct_3: str, oct_4: str) -> "MinerNetwork": + def from_octets( + cls, oct_1: str, oct_2: str, oct_3: str, oct_4: str + ) -> "MinerNetwork": """Parse 4 octet constructors into a MinerNetwork. Parameters: @@ -167,7 +169,9 @@ class MinerNetwork: try: return await ping_and_get_miner(ip) except ConnectionRefusedError: - tasks = [ping_and_get_miner(ip, port=port) for port in [4028, 4029, 8889]] + tasks = [ + ping_and_get_miner(ip, port=port) for port in [4028, 4029, 8889] + ] for miner in asyncio.as_completed(tasks): try: return await miner diff --git a/pyasic/settings/__init__.py b/pyasic/settings/__init__.py index 275b4920..520e6fef 100644 --- a/pyasic/settings/__init__.py +++ b/pyasic/settings/__init__.py @@ -13,10 +13,13 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ - -from typing import Any +import socket +import struct +from ssl import SSLContext +from typing import Any, Union import httpx +from httpx import AsyncHTTPTransport _settings = { # defaults "network_ping_retries": 1, @@ -32,14 +35,25 @@ _settings = { # defaults "default_bosminer_password": "root", "default_vnish_password": "admin", "default_goldshell_password": "123456789", + "so_linger_time": 1000, } +ssl_cxt = httpx.create_ssl_context() + + +def transport(verify: Union[str, bool, SSLContext] = ssl_cxt): + l_onoff = 1 + l_linger = get("so_linger_time", 1000) + + opts = [(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", l_onoff, l_linger))] + + return AsyncHTTPTransport(socket_options=opts, verify=verify) + + def get(key: str, other: Any = None) -> Any: return _settings.get(key, other) def update(key: str, val: Any) -> Any: _settings[key] = val - -ssl_cxt = httpx.create_ssl_context() \ No newline at end of file diff --git a/pyasic/web/antminer.py b/pyasic/web/antminer.py index 0eaf4113..145d55ce 100644 --- a/pyasic/web/antminer.py +++ b/pyasic/web/antminer.py @@ -38,10 +38,13 @@ class AntminerModernWebAPI(BaseWebAPI): url = f"http://{self.ip}/cgi-bin/{command}.cgi" auth = httpx.DigestAuth(self.username, self.pwd) try: - async with httpx.AsyncClient(verify=settings.ssl_cxt) as client: + async with httpx.AsyncClient(transport=settings.transport()) as client: if parameters: data = await client.post( - url, data=json.dumps(parameters), auth=auth, timeout=settings.get("api_function_timeout", 3) # noqa + url, + data=json.dumps(parameters), + auth=auth, + timeout=settings.get("api_function_timeout", 3), # noqa ) else: data = await client.get(url, auth=auth) @@ -57,7 +60,7 @@ class AntminerModernWebAPI(BaseWebAPI): async def multicommand( self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True ) -> dict: - async with httpx.AsyncClient(verify=settings.ssl_cxt) as client: + async with httpx.AsyncClient(transport=settings.transport()) as client: tasks = [ asyncio.create_task(self._handle_multicommand(client, command)) for command in commands @@ -149,10 +152,13 @@ class AntminerOldWebAPI(BaseWebAPI): url = f"http://{self.ip}/cgi-bin/{command}.cgi" auth = httpx.DigestAuth(self.username, self.pwd) try: - async with httpx.AsyncClient(verify=settings.ssl_cxt) as client: + async with httpx.AsyncClient(transport=settings.transport()) as client: if parameters: data = await client.post( - url, data=parameters, auth=auth, timeout=settings.get("api_function_timeout", 3) + url, + data=parameters, + auth=auth, + timeout=settings.get("api_function_timeout", 3), ) else: data = await client.get(url, auth=auth) @@ -170,7 +176,7 @@ class AntminerOldWebAPI(BaseWebAPI): ) -> dict: data = {k: None for k in commands} auth = httpx.DigestAuth(self.username, self.pwd) - async with httpx.AsyncClient(verify=settings.ssl_cxt) as client: + async with httpx.AsyncClient(transport=settings.transport()) as client: for command in commands: try: url = f"http://{self.ip}/cgi-bin/{command}.cgi" diff --git a/pyasic/web/bosminer/__init__.py b/pyasic/web/bosminer/__init__.py index 78206514..c23bd45a 100644 --- a/pyasic/web/bosminer/__init__.py +++ b/pyasic/web/bosminer/__init__.py @@ -186,7 +186,7 @@ class BOSMinerGQLAPI: if command.get("query") is None: query = {"query": self.parse_command(command)} try: - async with httpx.AsyncClient(verify=settings.ssl_cxt) as client: + async with httpx.AsyncClient(transport=settings.transport()) as client: await self.auth(client) data = await client.post(url, json=query) except httpx.HTTPError: @@ -239,7 +239,7 @@ class BOSMinerLuCIAPI: async def send_command(self, path: str, ignore_errors: bool = False) -> dict: try: - async with httpx.AsyncClient(verify=settings.ssl_cxt) as client: + async with httpx.AsyncClient(transport=settings.transport()) as client: await self.auth(client) data = await client.get( f"http://{self.ip}{path}", headers={"User-Agent": "BTC Tools v0.1"} diff --git a/pyasic/web/goldshell.py b/pyasic/web/goldshell.py index 47cf2fdf..f01abfad 100644 --- a/pyasic/web/goldshell.py +++ b/pyasic/web/goldshell.py @@ -31,7 +31,7 @@ class GoldshellWebAPI(BaseWebAPI): self.jwt = None async def auth(self): - async with httpx.AsyncClient(verify=settings.ssl_cxt) as client: + async with httpx.AsyncClient(transport=settings.transport()) as client: try: await client.get(f"http://{self.ip}/user/logout") auth = ( @@ -71,7 +71,7 @@ class GoldshellWebAPI(BaseWebAPI): parameters.pop("pool_pwd") if not self.jwt: await self.auth() - async with httpx.AsyncClient(verify=settings.ssl_cxt) as client: + async with httpx.AsyncClient(transport=settings.transport()) as client: for i in range(settings.get("get_data_retries", 1)): try: if parameters: @@ -102,7 +102,7 @@ class GoldshellWebAPI(BaseWebAPI): data = {k: None for k in commands} data["multicommand"] = True await self.auth() - async with httpx.AsyncClient(verify=settings.ssl_cxt) as client: + async with httpx.AsyncClient(transport=settings.transport()) as client: for command in commands: try: response = await client.get( diff --git a/pyasic/web/inno.py b/pyasic/web/inno.py index 3b691614..ac174df6 100644 --- a/pyasic/web/inno.py +++ b/pyasic/web/inno.py @@ -32,7 +32,7 @@ class InnosiliconWebAPI(BaseWebAPI): self.jwt = None async def auth(self): - async with httpx.AsyncClient(verify=settings.ssl_cxt) as client: + async with httpx.AsyncClient(transport=settings.transport()) as client: try: auth = await client.post( f"http://{self.ip}/api/auth", @@ -54,7 +54,7 @@ class InnosiliconWebAPI(BaseWebAPI): ) -> dict: if not self.jwt: await self.auth() - async with httpx.AsyncClient(verify=settings.ssl_cxt) as client: + async with httpx.AsyncClient(transport=settings.transport()) as client: for i in range(settings.get("get_data_retries", 1)): try: response = await client.post( @@ -90,7 +90,7 @@ class InnosiliconWebAPI(BaseWebAPI): data = {k: None for k in commands} data["multicommand"] = True await self.auth() - async with httpx.AsyncClient(verify=settings.ssl_cxt) as client: + async with httpx.AsyncClient(transport=settings.transport()) as client: for command in commands: try: response = await client.post( diff --git a/pyasic/web/vnish.py b/pyasic/web/vnish.py index b756d68a..881f0814 100644 --- a/pyasic/web/vnish.py +++ b/pyasic/web/vnish.py @@ -31,7 +31,7 @@ class VNishWebAPI(BaseWebAPI): self.token = None async def auth(self): - async with httpx.AsyncClient(verify=settings.ssl_cxt) as client: + async with httpx.AsyncClient(transport=settings.transport()) as client: try: auth = await client.post( f"http://{self.ip}/api/v1/unlock", @@ -58,7 +58,7 @@ class VNishWebAPI(BaseWebAPI): ) -> dict: if not self.token: await self.auth() - async with httpx.AsyncClient(verify=settings.ssl_cxt) as client: + async with httpx.AsyncClient(transport=settings.transport()) as client: for i in range(settings.get("get_data_retries", 1)): try: auth = self.token diff --git a/pyproject.toml b/pyproject.toml index 189741c7..33e0ae64 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,9 +9,9 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.8" -httpx = "^0.25.0" +httpx = "^0.25.2" asyncssh = "^2.14.1" -grpc-requests = "^0.1.11" +grpc-requests = "^0.1.12" passlib = "^1.7.4" pyaml = "^23.9.7" toml = "^0.10.2" diff --git a/tests/network_tests/__init__.py b/tests/network_tests/__init__.py index 772d353a..82ed166b 100644 --- a/tests/network_tests/__init__.py +++ b/tests/network_tests/__init__.py @@ -22,7 +22,7 @@ from pyasic.network import MinerNetwork class NetworkTest(unittest.TestCase): def test_net_range(self): - net_range_str = "192.168.1.29, 192.168.1.40-192.168.1.43, 192.168.1.60" + net_range_str = ["192.168.1.29", "192.168.1.40-43", "192.168.1.60"] net_range_list = [ "192.168.1.29", "192.168.1.40", @@ -32,8 +32,8 @@ class NetworkTest(unittest.TestCase): "192.168.1.60", ] - net_1 = list(MinerNetwork(net_range_str).get_network().hosts()) - net_2 = list(MinerNetwork(net_range_list).get_network().hosts()) + net_1 = list(MinerNetwork.from_list(net_range_list).hosts) + net_2 = list(MinerNetwork.from_list(net_range_str).hosts) correct_net = [ ipaddress.IPv4Address("192.168.1.29"), @@ -51,11 +51,9 @@ class NetworkTest(unittest.TestCase): net_1_str = "192.168.1.0" net_1_mask = "/29" - net_1 = list(MinerNetwork(net_1_str, mask=net_1_mask).get_network().hosts()) + net_1 = list(MinerNetwork.from_subnet(net_1_str + net_1_mask).hosts) - net_2 = list( - MinerNetwork("192.168.1.1-192.168.1.5, 192.168.1.6").get_network().hosts() - ) + net_2 = list(MinerNetwork.from_list(["192.168.1.1-5", "192.168.1.6"]).hosts) correct_net = [ ipaddress.IPv4Address("192.168.1.1"), @@ -70,11 +68,10 @@ class NetworkTest(unittest.TestCase): self.assertTrue(net_2 == correct_net) def test_net_defaults(self): - net = MinerNetwork() - net_obj = net.get_network() - self.assertEqual(net_obj, MinerNetwork("192.168.1.0", mask=24).get_network()) - - self.assertEqual(net_obj, net.get_network()) + net = MinerNetwork.from_subnet("192.168.1.1/24") + self.assertEqual( + net.hosts, list(ipaddress.ip_network("192.168.1.0/24").hosts()) + ) if __name__ == "__main__":