datacomshell 0.1.2__tar.gz → 0.26.5__tar.gz

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.
@@ -1,9 +1,10 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: datacomshell
3
- Version: 0.1.2
3
+ Version: 0.26.5
4
4
  Summary: Add your description here
5
5
  Author: Saven
6
6
  Author-email: Saven <2416844857@qq.com>
7
+ Requires-Dist: aiofiles>=25.1.0
7
8
  Requires-Dist: telnetlib3>=4.0.2
8
9
  Requires-Python: >=3.14
9
10
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "datacomshell"
3
- version = "0.1.2"
3
+ version = "0.26.5"
4
4
  description = "Add your description here"
5
5
  authors = [
6
6
  { name = "Saven", email = "2416844857@qq.com" },
@@ -8,6 +8,7 @@ authors = [
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.14"
10
10
  dependencies = [
11
+ "aiofiles>=25.1.0",
11
12
  "telnetlib3>=4.0.2",
12
13
  ]
13
14
 
@@ -0,0 +1,259 @@
1
+ from typing import Optional, Union, Tuple, cast
2
+ from datacomshell.Auth import Auth, AuthState, UsernamePasswordAuth
3
+ from telnetlib3 import TelnetReader, TelnetReaderUnicode, TelnetWriter, TelnetWriterUnicode, open_connection
4
+ from datetime import datetime
5
+ import asyncio
6
+ import aiofiles
7
+ from aiofiles.threadpool.text import AsyncTextIOWrapper
8
+ import pathlib
9
+
10
+ import re
11
+
12
+ _DEFAULT_ENDING = '[saven-process-end]'
13
+ # 默认认证(无认证)
14
+ _DEFAILT_AUTH_OBG = Auth()
15
+
16
+ # type
17
+ _READER_TYPE = Union[TelnetReader, TelnetReaderUnicode]
18
+ _WRITER_TYPE = Union[TelnetWriter, TelnetWriterUnicode]
19
+
20
+ _DEFAULT_WAIT_INPUT_TAG_PARTTEN_LIST = [
21
+ re.compile(r'\<.*\>'),
22
+ re.compile(r'\[.*\]'),
23
+ ]
24
+
25
+
26
+ # __reader、__writer等操作对象为 null
27
+ class OperationObjNullError(RuntimeError):
28
+ def __init__(self, message: str) -> None: return super().__init__(message)
29
+
30
+
31
+ class ConnectParamError(RuntimeError):
32
+ def __init__(self, message: str) -> None: return super().__init__(message)
33
+
34
+
35
+ class LoginTimoutError(RuntimeError):
36
+ def __init__(self, message: str) -> None: return super().__init__(message)
37
+
38
+
39
+ class LoginTypeError(RuntimeError):
40
+ def __init__(self, message: str) -> None: return super().__init__(message)
41
+
42
+
43
+ class ExcCommandTimoutError(RuntimeError):
44
+ def __init__(self, message: str) -> None: return super().__init__(message)
45
+
46
+
47
+ class AsyncTelnetClientUtil(object):
48
+ """
49
+ AsyncTelnetClientUtil
50
+ """
51
+
52
+ def __init__(self,
53
+ host: str = '127.0.0.1',
54
+ port: int = 23,
55
+ auth: Auth = _DEFAILT_AUTH_OBG,
56
+ connect_timeout: float = 10.0,
57
+ login_timeout: float = 10.0,
58
+ exc_command_timeout: float = 10.0,
59
+ log_folder_path: Optional[str] = None,
60
+ log_new_second: float = 1800,
61
+ wait_input_tag_partten_list: list[re.Pattern] = _DEFAULT_WAIT_INPUT_TAG_PARTTEN_LIST,
62
+ ) -> None:
63
+ self.__host: str = host
64
+ self.__port: int = port
65
+ self.__connect_timeout: float = connect_timeout
66
+ self.__login_timeout: float = login_timeout
67
+ self.__exc_command_timout: float = exc_command_timeout
68
+
69
+ self.__auth: Auth = auth
70
+ self.__auth_type: AuthState = self.__auth.get_auth_type()
71
+
72
+ self.__reader: _READER_TYPE | None = None
73
+ self.__writer: _WRITER_TYPE | None = None
74
+
75
+ self.__log_folder_path: str | None = log_folder_path
76
+ self.__log_new_second: float = log_new_second
77
+ self.__timestemp: float = datetime.now().timestamp()
78
+ self.__log_file: AsyncTextIOWrapper | None = None
79
+
80
+ self.__is_log = True if log_folder_path != None else False
81
+
82
+ if self.__is_log and self.__log_folder_path != None:
83
+ pathlib.Path(self.__log_folder_path).mkdir(
84
+ parents=True, exist_ok=True)
85
+
86
+ async def __initConnect(self) -> None:
87
+ if self.__auth_type == AuthState.NONE_AUTH:
88
+ res = await self.__none_auth()
89
+ self.__reader = res[0]
90
+ self.__writer = res[1]
91
+ elif self.__auth_type == AuthState.USERNAME_PASSWORD_AUTH:
92
+ res = await self.__username_password_auth()
93
+ self.__reader = res[0]
94
+ self.__writer = res[1]
95
+ else:
96
+ raise LoginTypeError("Auth type error")
97
+
98
+ @classmethod
99
+ async def create(cls,
100
+ host: str = '127.0.0.1',
101
+ port: int = 23,
102
+ auth: Auth = _DEFAILT_AUTH_OBG,
103
+ connect_timeout: float = 10.0,
104
+ login_timeout: float = 10.0,
105
+ exc_command_timeout: float = 10.0,
106
+ log_folder_path: Optional[str] = None,
107
+ log_new_second: float = 1800,
108
+ wait_input_tag_partten_list: list[re.Pattern] = _DEFAULT_WAIT_INPUT_TAG_PARTTEN_LIST,) -> 'AsyncTelnetClientUtil':
109
+
110
+ instance: AsyncTelnetClientUtil = cls(
111
+ host=host,
112
+ port=port,
113
+ auth=auth,
114
+ connect_timeout=connect_timeout,
115
+ login_timeout=login_timeout,
116
+ exc_command_timeout=exc_command_timeout,
117
+ log_folder_path=log_folder_path,
118
+ log_new_second=log_new_second,
119
+ wait_input_tag_partten_list=wait_input_tag_partten_list,
120
+ )
121
+
122
+ await instance.__initConnect()
123
+ await instance.__create_log_file()
124
+ print(f"""
125
+ [I] connect success: {instance.__host}:{instance.__port}
126
+ [I] login type: {instance.__auth.get_auth_type().name}
127
+ [I] login info: {instance.__auth.to_string()}
128
+ """)
129
+ return instance
130
+
131
+ def __command_wrapper(self, command: str) -> str:
132
+ return command + '\r\n'
133
+
134
+ async def __read_wait_wrapper(self, read) -> bytes:
135
+ return await asyncio.wait_for(read, timeout=self.__exc_command_timout)
136
+
137
+ async def __read_login_wait_wrapper(self, read):
138
+ return await asyncio.wait_for(read, timeout=self.__login_timeout)
139
+
140
+ async def __none_auth(self) -> Tuple[TelnetReader, TelnetWriter]:
141
+ return await open_connection(self.__host, self.__port, connect_timeout=self.__connect_timeout)
142
+
143
+ async def __login_of_username_password(self, reader: _READER_TYPE, writer: _WRITER_TYPE) -> None:
144
+ if reader == None and writer == None:
145
+ raise OperationObjNullError("[IE]: __reader and __writer is None")
146
+
147
+ if self.__auth_type != AuthState.USERNAME_PASSWORD_AUTH:
148
+ raise ConnectParamError(
149
+ "[IE] __auth_type is not AuthState.USERNAME_PASSWORD_AUTH")
150
+
151
+ # 变量类型整理
152
+ _reader = reader
153
+ _writer = cast(TelnetWriterUnicode, writer)
154
+ _auth = cast(UsernamePasswordAuth, self.__auth)
155
+
156
+ buffer = ''
157
+ try:
158
+ res: bytes = b''
159
+ res: bytes = await self.__read_login_wait_wrapper(_reader.readuntil(b'Username:'))
160
+ buffer = buffer + res.decode()
161
+ _writer.write(self.__command_wrapper(_auth.get_username()))
162
+ await _writer.drain()
163
+
164
+ res: bytes = b''
165
+ res: bytes = await self.__read_login_wait_wrapper(_reader.readuntil(b'Password:'))
166
+ buffer = buffer + res.decode()
167
+ _writer.write(self.__command_wrapper(_auth.get_password()))
168
+ await _writer.drain()
169
+
170
+ res: bytes = b''
171
+ res: bytes = await self.__read_login_wait_wrapper(_reader.readuntil(b'>'))
172
+ buffer = buffer + res.decode()
173
+
174
+ # Log
175
+ await self.__log(buffer)
176
+ except asyncio.TimeoutError:
177
+ raise LoginTimoutError('Login timeout')
178
+
179
+ async def __username_password_auth(self) -> Tuple[TelnetReader, TelnetWriter]:
180
+ reader, writer = await open_connection(self.__host, self.__port, connect_timeout=self.__connect_timeout)
181
+
182
+ # 登录
183
+ await self.__login_of_username_password(reader, writer)
184
+ return reader, writer
185
+
186
+ async def __close_log_file(self):
187
+ if self.__log_file != None:
188
+ await self.__log_file.close()
189
+
190
+ async def __create_log_file(self):
191
+ await self.__close_log_file()
192
+ filename = datetime.now().strftime('%Y-%m-%d_%H-%M-%S-%f') + '.log'
193
+
194
+ if self.__log_folder_path == None:
195
+ raise RuntimeError('[IE] log_folder_path error')
196
+
197
+ self.__timestemp = datetime.now().timestamp()
198
+ en_path = self.__log_folder_path + '/' + filename
199
+ self.__log_file = await aiofiles.open(en_path, 'a', encoding='utf-8')
200
+
201
+ await self.__log_file.write(f'================ {filename} ================')
202
+
203
+ async def __log(self, message: str):
204
+ if self.__is_log != True:
205
+ return
206
+
207
+ curren_timestemp: float = datetime.now().timestamp()
208
+ diff = curren_timestemp - self.__timestemp
209
+
210
+ # 新建
211
+ if diff > self.__log_new_second:
212
+ await self.__create_log_file()
213
+
214
+ # 记录
215
+ if self.__log_file == None:
216
+ raise RuntimeError('[IE] log_file is None')
217
+ else:
218
+ await self.__log_file.write(message)
219
+
220
+ async def execute(self, command: str) -> bytes:
221
+ if self.__reader == None and self.__writer == None:
222
+ raise OperationObjNullError("[IE]: __reader and __writer is None")
223
+
224
+ # 变量类型整理
225
+ _reader = cast(_READER_TYPE, self.__reader)
226
+ _writer = cast(TelnetWriterUnicode, self.__writer)
227
+
228
+ _writer.write('\r\n')
229
+ _writer.write(self.__command_wrapper(command))
230
+ _writer.write(_DEFAULT_ENDING)
231
+ await _writer.drain()
232
+
233
+ res = b'null'
234
+ try:
235
+ res = await self.__read_wait_wrapper(_reader.readuntil(_DEFAULT_ENDING.encode('utf-8')))
236
+ await self.__log(res.decode())
237
+ except asyncio.TimeoutError as e:
238
+ print("[I] reader buffer empty")
239
+
240
+ return res
241
+
242
+ async def execute_no_back(self, command: str) -> None:
243
+ await self.execute(command)
244
+
245
+ async def close(self) -> None:
246
+ if self.__reader == None and self.__writer == None:
247
+ raise OperationObjNullError("[IE]: __reader and __writer is None")
248
+
249
+ # 变量类型整理
250
+ _reader = cast(_READER_TYPE, self.__reader)
251
+ _writer = cast(TelnetWriterUnicode, self.__writer)
252
+
253
+ if self.__log_file != None:
254
+ await self.__log_file.close()
255
+
256
+ _reader.close()
257
+ _writer.close()
258
+
259
+ await _writer.wait_closed()
@@ -0,0 +1,46 @@
1
+ from enum import Enum
2
+
3
+
4
+ class AuthState(Enum):
5
+ NONE_AUTH = 0
6
+ USERNAME_PASSWORD_AUTH = 1
7
+ SECRET_AUTH = 2
8
+
9
+
10
+ class Auth(object):
11
+ def __init__(self, auth: AuthState = AuthState.NONE_AUTH) -> None:
12
+ self.__type = auth
13
+
14
+ def get_auth_type(self) -> AuthState:
15
+ return self.__type
16
+
17
+ def to_string(self) -> str:
18
+ return 'NONE'
19
+
20
+
21
+ class UsernamePasswordAuth(Auth):
22
+ def __init__(self, username: str, password: str) -> None:
23
+ super().__init__(auth=AuthState.USERNAME_PASSWORD_AUTH)
24
+ self.__username = username
25
+ self.__password = password
26
+
27
+ def get_username(self) -> str:
28
+ return self.__username
29
+
30
+ def get_password(self) -> str:
31
+ return self.__password
32
+
33
+ def to_string(self) -> str:
34
+ return f'{self.__username}&{self.__password}'
35
+
36
+
37
+ class SecretAuth(Auth):
38
+ def __init__(self, secret: str) -> None:
39
+ super().__init__(auth=AuthState.SECRET_AUTH)
40
+ self.__secret = secret
41
+
42
+ def get_secret(self) -> str:
43
+ return self.__secret
44
+
45
+ def to_string(self) -> str:
46
+ return self.__secret
@@ -1,175 +0,0 @@
1
- import asyncio
2
- import re
3
- from typing import Optional
4
- import telnetlib3
5
- from telnetlib3 import TelnetWriter, TelnetReader, TelnetWriterUnicode, TelnetReaderUnicode
6
-
7
-
8
- class ConnectError(RuntimeError):
9
- def __init__(self, message: str) -> None:
10
- super().__init__(message)
11
- pass
12
-
13
-
14
- class LoginError(RuntimeError):
15
- def __init__(self, message: str) -> None:
16
- super().__init__(message)
17
- pass
18
-
19
-
20
- _CONST_COMMAND_RUNNED_PATTERN = re.compile(
21
- r'<.*>|\[.*\]|<~.*>|\[~.*\]|<\*.*>|\[\*.*\]')
22
- _CONST_COMMAND_RUNNED_PATTERN_LIST = [_CONST_COMMAND_RUNNED_PATTERN]
23
-
24
- _CONST_COMMAND_ERROR_PATTERN = re.compile(r'%.*')
25
- _CONST_COMMAND_ERROR_PATTERN_LIST = [_CONST_COMMAND_ERROR_PATTERN]
26
-
27
-
28
- class AsyncTelnetUtil(object):
29
- def __init__(self,
30
- host: str,
31
- username: str,
32
- password: str,
33
- port: int = 23,
34
- connect_timeout: Optional[float] = None
35
- ) -> None:
36
- # 连接基本信息
37
- self.__host = host
38
- self.__port = port
39
- self.__connect_timeout = connect_timeout
40
-
41
- self.__username = username
42
- self.__password = password
43
-
44
- self.__login_status = False
45
- self.__reader: TelnetReader | TelnetReaderUnicode | None = None
46
- self.__writer: TelnetWriter | TelnetWriterUnicode | None = None
47
-
48
- # 默认命令执行匹配
49
- self.__DEFAULT_CONST_COMMAND_RUNNED_PATTERN_LIST = [
50
- _CONST_COMMAND_RUNNED_PATTERN]
51
-
52
- self.__DEFAULT_CONST_COMMAND_ERROR_PATTERN_LIST = [
53
- _CONST_COMMAND_ERROR_PATTERN]
54
-
55
- def __successfulMatch(self, pattern_list: Optional[list[re.Pattern]] = None, buffer: str = '') -> bool:
56
- if pattern_list is not None:
57
- for pattern in pattern_list:
58
- if re.search(pattern=pattern, string=buffer) is not None:
59
- return True
60
- else:
61
- for pattern in self.__DEFAULT_CONST_COMMAND_RUNNED_PATTERN_LIST:
62
- if re.search(pattern=pattern, string=buffer) is not None:
63
- return True
64
-
65
- return False
66
-
67
- def __errorMatch(self, error_pattern_list: Optional[list[re.Pattern]] = None, buffer: str = '') -> bool:
68
- if error_pattern_list is not None:
69
- for pattern in error_pattern_list:
70
- if re.search(pattern=pattern, string=buffer) is not None:
71
- return True
72
- else:
73
- for pattern in self.__DEFAULT_CONST_COMMAND_ERROR_PATTERN_LIST:
74
- if re.search(pattern=pattern, string=buffer) is not None:
75
- return True
76
-
77
- return False
78
-
79
- # 读取命令返回数据
80
- async def __read(self,
81
- pattern_list: Optional[list[re.Pattern]] = None,
82
- error_pattern_list: Optional[list[re.Pattern]] = None,
83
- timeout: Optional[float] = 10.0,
84
- size: int = 1024
85
- ) -> str | None:
86
- buffer = ''
87
- runStatus = True
88
-
89
- if self.__reader is None or self.__writer is None:
90
- raise ConnectError("Not connected to device")
91
-
92
- while runStatus:
93
- try:
94
- res = await asyncio.wait_for(self.__reader.read(size), timeout)
95
- except asyncio.TimeoutError:
96
- break
97
-
98
- if not res:
99
- break
100
-
101
- if res is bytes:
102
- buffer += res.decode('utf-8')
103
- elif res is str:
104
- buffer += res
105
-
106
- __temp_match_status = False
107
- if not __temp_match_status:
108
- __temp_match_status = self.__successfulMatch(
109
- pattern_list=pattern_list, buffer=buffer)
110
-
111
- if not __temp_match_status:
112
- __temp_match_status = self.__errorMatch(
113
- error_pattern_list=error_pattern_list, buffer=buffer)
114
-
115
- if __temp_match_status:
116
- runStatus = False
117
-
118
- return buffer if runStatus else None
119
-
120
- # 写入命令
121
- def __write(self, command: str) -> None:
122
- if self.__writer is None:
123
- raise ConnectError("Not connected to device")
124
-
125
- in_command = command + "\r\n"
126
-
127
- # 根据 writer 类型决定使用字符串还是字节
128
- if isinstance(self.__writer, TelnetWriterUnicode):
129
- self.__writer.write(in_command)
130
- else:
131
- self.__writer.write(in_command.encode('utf-8'))
132
-
133
- # 建立连接
134
- async def connect(self) -> None:
135
- try:
136
- self.__reader, self.__writer = await telnetlib3.open_connection(
137
- host=self.__host, port=self.__port, connect_timeout=self.__connect_timeout)
138
- except asyncio.TimeoutError:
139
- raise TimeoutError(
140
- f"Connection to {self.__host}:{self.__port} timed out after {self.__connect_timeout} seconds")
141
-
142
- if self.__reader is None or self.__writer is None:
143
- raise ConnectionError("Failed to connect to device")
144
-
145
- return
146
-
147
- # 建立登录
148
- async def login(self) -> None:
149
- if self.__login_status:
150
- return
151
-
152
- if await self.__read(pattern_list=[re.compile(r"Username:")]) is not None:
153
- self.__write(self.__username)
154
- else:
155
- raise LoginError("Failed to connect to device")
156
-
157
- if await self.__read(pattern_list=[re.compile(r"Password:")]) is not None:
158
- self.__write(self.__password)
159
- else:
160
- raise LoginError("Failed to connect to device")
161
-
162
- if await self.__read(pattern_list=self.__DEFAULT_CONST_COMMAND_RUNNED_PATTERN_LIST) is not None:
163
- self.__login_status = True
164
- return
165
-
166
- async def send_command(self, command: str):
167
- self.__write(command)
168
- return await self.__read()
169
-
170
- def only_send_command(self, command: str) -> None:
171
- self.__write(command)
172
-
173
- async def build(self) -> None:
174
- await self.connect()
175
- await self.login()
@@ -1,3 +0,0 @@
1
-
2
- class TelnetShellClient(object):
3
- pass
@@ -1,39 +0,0 @@
1
- import uuid
2
-
3
- __EMPTY_SYNC_PIPELINE_INDEX = -1
4
-
5
-
6
- class Command(object):
7
- def __init__(self, command_str: str) -> None:
8
- self.__command_id = None
9
- self.__command_str = command_str
10
- self.__command_has_result = True
11
- self.__command_result = None
12
-
13
-
14
- class TelnetShellServer(object):
15
- """
16
- Telnet shell server.
17
- """
18
-
19
- def __init__(self) -> None:
20
-
21
- self.__sync_command_pipeline = []
22
- self.__async_command_pipeline = []
23
- self.__command_runned_result_dict = {}
24
-
25
- self.__sync_current_command_index = __EMPTY_SYNC_PIPELINE_INDEX
26
-
27
- self.__connect_pool = []
28
-
29
- def __check_reset_result_dict_count(self) -> bool:
30
- if self.__command_runned_result_dict.__len__() > 1000:
31
- self.__command_runned_result_dict.clear()
32
- return True
33
- return False
34
-
35
- def gen_command_process_id(self) -> uuid.UUID:
36
- return uuid.uuid4()
37
-
38
- def sync_push(self, command: Command) -> None:
39
- self.__sync_command_pipeline.insert(0, command)
File without changes