gnetclisdk 1.0.101__py3-none-any.whl → 1.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of gnetclisdk might be problematic. Click here for more details.

gnetclisdk/config.py ADDED
@@ -0,0 +1,54 @@
1
+ from dataclasses import dataclass, asdict, field
2
+ from datetime import timedelta
3
+
4
+ import yaml
5
+
6
+
7
+ @dataclass
8
+ class LogConfig:
9
+ level: str = "info"
10
+ json: bool = False
11
+
12
+
13
+ @dataclass
14
+ class AuthAppConfig:
15
+ login: str = ""
16
+ password: str = ""
17
+ private_key: str = "" # path to private key file
18
+ proxy_jump: str = ""
19
+ use_agent: bool = False
20
+ ssh_config: bool = False
21
+
22
+
23
+ @dataclass
24
+ class Config:
25
+ logging: LogConfig = field(default_factory=LogConfig)
26
+ port: str = "" # Listen address
27
+ http_port: str = "" # Http listen address
28
+ dev_auth: AuthAppConfig = field(default_factory=AuthAppConfig)
29
+ dev_conf: str = "" # Path to yaml with device types
30
+ tls: bool = False
31
+ cert_file: str = ""
32
+ key_file: str = ""
33
+ basic_auth: str = ""
34
+ disable_tcp: bool = False
35
+ unix_socket: str = "" # Unix socket pat
36
+ default_read_timeout: timedelta = timedelta(seconds=0)
37
+ default_cmd_timeout: timedelta = timedelta(seconds=0)
38
+
39
+
40
+ def dict_factory(data):
41
+ return {
42
+ key: (
43
+ f"{value.total_seconds()}s"
44
+ if isinstance(value, timedelta)
45
+ else value
46
+ )
47
+ for key, value in data
48
+ if value != "" # skip empty strings
49
+ }
50
+
51
+
52
+ def config_to_yaml(cfg: Config) -> str:
53
+ cfg_dict = asdict(cfg, dict_factory=dict_factory)
54
+ return yaml.safe_dump(cfg_dict, sort_keys=False)
gnetclisdk/starter.py ADDED
@@ -0,0 +1,121 @@
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ from asyncio.subprocess import Process
5
+ from json import JSONDecodeError
6
+ from subprocess import DEVNULL, PIPE
7
+
8
+ from gnetclisdk.config import Config, LogConfig, AuthAppConfig, config_to_yaml
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ DEFAULT_GNETCLI_SERVER_CONF = Config(
13
+ logging=LogConfig(level="debug", json=True),
14
+ dev_auth=AuthAppConfig(use_agent=True, ssh_config=True),
15
+ )
16
+
17
+
18
+ class GnetcliStarter:
19
+ def __init__(
20
+ self,
21
+ server_path: str,
22
+ server_conf: Config = DEFAULT_GNETCLI_SERVER_CONF,
23
+ start_timeout: int = 5,
24
+ stop_timeout: int = 10,
25
+ ):
26
+ self._server_path = server_path
27
+ self._server_conf = server_conf
28
+ self._running = False
29
+ self._proc: Process | None = None
30
+ self._start_timeout = start_timeout
31
+ self._stop_timeout = stop_timeout
32
+ self._reader_task: asyncio.Task | None = None
33
+
34
+ async def _start(self) -> Process:
35
+ logger.debug("Starting Gnetcli server: %s", self._server_path)
36
+ proc = await asyncio.create_subprocess_exec(
37
+ self._server_path,
38
+ "--conf-file",
39
+ "-",
40
+ stdout=DEVNULL, # we do not read stdout
41
+ stderr=PIPE,
42
+ stdin=PIPE,
43
+ )
44
+ self._running = True
45
+ conf_yaml = config_to_yaml(self._server_conf)
46
+ proc.stdin.write(conf_yaml.encode())
47
+ await proc.stdin.drain()
48
+ proc.stdin.close()
49
+ return proc
50
+
51
+ async def _wait_url(self) -> str:
52
+ while proc := self._proc:
53
+ output = await proc.stderr.readline()
54
+ if not output and proc.returncode is not None:
55
+ break
56
+ if not output:
57
+ continue
58
+ logger.debug("gnetcli output: %s", output.strip())
59
+ try:
60
+ data = json.loads(output)
61
+ except JSONDecodeError:
62
+ logger.error("cannot decode data")
63
+ continue
64
+ if data.get("msg") == "init tcp socket":
65
+ logger.debug("Tcp socket found")
66
+ return data.get("address")
67
+ if data.get("msg") == "init unix socket":
68
+ logger.debug("Unix socket found")
69
+ return "unix:" + data.get("path")
70
+ if data.get("level") == "panic":
71
+ logger.error("gnetcli error %s", data)
72
+ return "" # stopped
73
+
74
+ async def __aenter__(self) -> str:
75
+ self._proc = await self._start()
76
+ try:
77
+ url = await asyncio.wait_for(
78
+ self._wait_url(), timeout=self._start_timeout
79
+ )
80
+ except asyncio.TimeoutError:
81
+ logger.error("gnetcli _wait_url timeout, terminating")
82
+ await self._terminate()
83
+ raise RuntimeError("gnetcli start failed")
84
+ logger.info("gnetcli started with url: %s", url)
85
+ self._reader_task = asyncio.create_task(self._communicate())
86
+ return url
87
+
88
+ async def _communicate(self) -> None:
89
+ while proc := self._proc:
90
+ output = await proc.stderr.readline()
91
+ if not output and proc.returncode is not None:
92
+ logger.debug("stop reading, gnetcli terminated")
93
+ return
94
+ if not output:
95
+ continue
96
+ logger.debug("gnetcli output: %s", output.strip())
97
+
98
+ async def _terminate(self) -> None:
99
+ if (proc := self._proc) is None:
100
+ return
101
+ if proc.returncode is not None:
102
+ logger.error(
103
+ "gnetcli already terminated with code: %s", proc.returncode
104
+ )
105
+ return
106
+ logger.debug("terminate gnetcli")
107
+ proc.terminate()
108
+ try:
109
+ await asyncio.wait_for(proc.wait(), timeout=self._stop_timeout)
110
+ except TimeoutError:
111
+ logger.debug("gnetcli terminate failed, killing")
112
+ self._proc.kill()
113
+ logger.debug("gnetcli terminated with code: %s", proc.returncode)
114
+ if self._reader_task is not None and not self._reader_task.cancel() and not self._reader_task.cancelling():
115
+ self._reader_task.cancel()
116
+ self._reader_task = None
117
+ self._proc = None
118
+
119
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
120
+ self.running = False
121
+ await self._terminate()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gnetclisdk
3
- Version: 1.0.101
3
+ Version: 1.1.1
4
4
  Summary: Client for Gnetcli GRPC-server
5
5
  Home-page: https://github.com/annetutil/gnetcli
6
6
  Author: Alexander Balezin
@@ -16,6 +16,7 @@ Description-Content-Type: text/markdown
16
16
  Requires-Dist: protobuf>=4.24.4
17
17
  Requires-Dist: grpcio>=1.59.2
18
18
  Requires-Dist: googleapis-common-protos>=1.61.0
19
+ Requires-Dist: pyyaml
19
20
  Dynamic: author
20
21
  Dynamic: author-email
21
22
  Dynamic: classifier
@@ -1,11 +1,13 @@
1
1
  gnetclisdk/auth.py,sha256=GwM7H7Ecb-gwqUTkQorifNB_mtnZfgeS46gOW2Vx1U4,1246
2
2
  gnetclisdk/client.py,sha256=5LEIvHqWk2EVSw4xsaVWPOFahPAiuMObMe2bb9YStu8,19806
3
+ gnetclisdk/config.py,sha256=8Zj9oJdZXE27fMHQy2DBdC6KXdL8flDQ2w3fgc7xXNs,1364
3
4
  gnetclisdk/exceptions.py,sha256=Cg94IdDAzQ9Q56Tdif6ladV1IcdBUg0TPa54OBVhJD0,4038
4
5
  gnetclisdk/interceptors.py,sha256=apj3l4lnR2ZcsA49odptrBC0kTDmP6Mp0EzYkeEJz9Y,7010
6
+ gnetclisdk/starter.py,sha256=xXwCNicAT2i3gXxI91uc0GlhWbkqxa4fJwTSPeLJJWQ,4307
5
7
  gnetclisdk/proto/server_pb2.py,sha256=v_A3rRjuWKa7rPOrsAPOl_oLmfD4sqT5ELpAyM3cUI0,8126
6
8
  gnetclisdk/proto/server_pb2.pyi,sha256=66Oq_YbkGgEQt9ea-P64H58hiUZoMhqptlMQdl2IAaw,8402
7
9
  gnetclisdk/proto/server_pb2_grpc.py,sha256=H_CmevWcjvGTvp4TmzEV-TJRPq2SsQ6UgoCDc-hosyo,12805
8
- gnetclisdk-1.0.101.dist-info/METADATA,sha256=UjVBq7HFXlt_Bx3Vbo9clVAuGGoCfPU5Un02DT68Uao,1794
9
- gnetclisdk-1.0.101.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
- gnetclisdk-1.0.101.dist-info/top_level.txt,sha256=MNjS8LEt6d2rZ-dUbV2cnqkuTMu3EqEL2eiSvUZuUlA,11
11
- gnetclisdk-1.0.101.dist-info/RECORD,,
10
+ gnetclisdk-1.1.1.dist-info/METADATA,sha256=FfBiCWzQbAhs0ww0KqsyguLbt_vjvoJNmY1rBGC4t18,1814
11
+ gnetclisdk-1.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ gnetclisdk-1.1.1.dist-info/top_level.txt,sha256=MNjS8LEt6d2rZ-dUbV2cnqkuTMu3EqEL2eiSvUZuUlA,11
13
+ gnetclisdk-1.1.1.dist-info/RECORD,,