arpakitlib 1.8.332__py3-none-any.whl → 1.8.336__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 arpakitlib might be problematic. Click here for more details.
- arpakitlib/ar_ssh_runner_util.py +391 -0
- {arpakitlib-1.8.332.dist-info → arpakitlib-1.8.336.dist-info}/METADATA +6 -2
- {arpakitlib-1.8.332.dist-info → arpakitlib-1.8.336.dist-info}/RECORD +6 -5
- {arpakitlib-1.8.332.dist-info → arpakitlib-1.8.336.dist-info}/WHEEL +0 -0
- {arpakitlib-1.8.332.dist-info → arpakitlib-1.8.336.dist-info}/entry_points.txt +0 -0
- {arpakitlib-1.8.332.dist-info → arpakitlib-1.8.336.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
# arpakit
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
from datetime import timedelta
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import asyncssh
|
|
11
|
+
import paramiko
|
|
12
|
+
from arpakitlib.ar_json_util import transfer_data_to_json_str
|
|
13
|
+
from pydantic import BaseModel, ConfigDict
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _BaseSSHException(Exception):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ConnectionSSHException(_BaseSSHException):
|
|
21
|
+
def __init__(self, ssh_runner: SSHRunner, base_exception: Exception | None = None):
|
|
22
|
+
self.ssh_runner = ssh_runner
|
|
23
|
+
self.base_exception = base_exception
|
|
24
|
+
|
|
25
|
+
def __str__(self):
|
|
26
|
+
parts = [
|
|
27
|
+
f"Connection error",
|
|
28
|
+
f"{self.ssh_runner.username}@{self.ssh_runner.hostname}:{self.ssh_runner.port}",
|
|
29
|
+
f"{type(self.base_exception)=}",
|
|
30
|
+
f"{self.base_exception=}"
|
|
31
|
+
]
|
|
32
|
+
return ', '.join(parts)
|
|
33
|
+
|
|
34
|
+
def __repr__(self):
|
|
35
|
+
return str(self)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ErrorInRunSSHException(_BaseSSHException):
|
|
39
|
+
def __init__(self, ssh_runner: SSHRunner, base_exception: Exception | None = None, message: str | None = None):
|
|
40
|
+
self.ssh_runner = ssh_runner
|
|
41
|
+
self.base_exception = base_exception
|
|
42
|
+
self.message = message
|
|
43
|
+
|
|
44
|
+
def __str__(self):
|
|
45
|
+
parts = [
|
|
46
|
+
f"Error in run",
|
|
47
|
+
f"{self.ssh_runner.username}@{self.ssh_runner.hostname}:{self.ssh_runner.port}",
|
|
48
|
+
]
|
|
49
|
+
if self.base_exception is not None:
|
|
50
|
+
parts.append(f"{self.base_exception=}")
|
|
51
|
+
if self.message is not None:
|
|
52
|
+
parts.append(f"{self.message=}")
|
|
53
|
+
return ', '.join(parts)
|
|
54
|
+
|
|
55
|
+
def __repr__(self):
|
|
56
|
+
return str(self)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class SSHRunResultHasErrorSSHException(_BaseSSHException):
|
|
60
|
+
def __init__(self, ssh_run_result: SSHRunResult, message: str | None = None):
|
|
61
|
+
self.ssh_run_result = ssh_run_result
|
|
62
|
+
self.message = message
|
|
63
|
+
|
|
64
|
+
def __str__(self):
|
|
65
|
+
parts = [
|
|
66
|
+
f"SSHRunResult has error",
|
|
67
|
+
f"{self.ssh_run_result.ssh_runner.username}@{self.ssh_run_result.ssh_runner.hostname}:{self.ssh_run_result.ssh_runner.port}",
|
|
68
|
+
f"return_code={str(self.ssh_run_result.return_code)}",
|
|
69
|
+
f"err={str(self.ssh_run_result.err)}"
|
|
70
|
+
]
|
|
71
|
+
if self.message is not None:
|
|
72
|
+
parts.append(f"message={self.message}")
|
|
73
|
+
return ', '.join(parts)
|
|
74
|
+
|
|
75
|
+
def __repr__(self):
|
|
76
|
+
return str(self)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class SSHRunResult(BaseModel):
|
|
80
|
+
out: str
|
|
81
|
+
err: str
|
|
82
|
+
return_code: int | None = None
|
|
83
|
+
ssh_runner: SSHRunner
|
|
84
|
+
|
|
85
|
+
model_config = ConfigDict(extra="forbid", arbitrary_types_allowed=True, from_attributes=True)
|
|
86
|
+
|
|
87
|
+
def simple_dict(self) -> dict[str, Any]:
|
|
88
|
+
return {
|
|
89
|
+
"out": self.out,
|
|
90
|
+
"err": self.err,
|
|
91
|
+
"return_code": self.return_code,
|
|
92
|
+
"has_bad_return_code": self.has_bad_return_code,
|
|
93
|
+
"has_err": self.has_err,
|
|
94
|
+
"has_out": self.has_out,
|
|
95
|
+
"username": self.ssh_runner.username,
|
|
96
|
+
"hostname": self.ssh_runner.hostname,
|
|
97
|
+
"port": self.ssh_runner.port,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
def simple_json(self) -> str:
|
|
101
|
+
return transfer_data_to_json_str(
|
|
102
|
+
self.simple_dict(),
|
|
103
|
+
beautify=True,
|
|
104
|
+
fast=False
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def __repr__(self) -> str:
|
|
108
|
+
return self.simple_json()
|
|
109
|
+
|
|
110
|
+
def __str__(self) -> str:
|
|
111
|
+
return self.simple_json()
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def has_bad_return_code(self) -> bool:
|
|
115
|
+
if self.return_code is None:
|
|
116
|
+
return False
|
|
117
|
+
return self.return_code != 0
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def has_err(self) -> bool:
|
|
121
|
+
if self.err:
|
|
122
|
+
return True
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def has_out(self) -> bool:
|
|
127
|
+
if self.out:
|
|
128
|
+
return True
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
def raise_for_bad_return_code(self):
|
|
132
|
+
if self.has_bad_return_code:
|
|
133
|
+
raise SSHRunResultHasErrorSSHException(ssh_run_result=self)
|
|
134
|
+
|
|
135
|
+
def raise_for_err(self):
|
|
136
|
+
if self.has_err:
|
|
137
|
+
raise SSHRunResultHasErrorSSHException(ssh_run_result=self)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class SSHRunner:
|
|
141
|
+
|
|
142
|
+
def __init__(
|
|
143
|
+
self,
|
|
144
|
+
*,
|
|
145
|
+
username: str = "root",
|
|
146
|
+
hostname: str, # ipv4, ipv6, domain
|
|
147
|
+
port: int = 22,
|
|
148
|
+
password: str | None = None,
|
|
149
|
+
base_timeout: float | None = None,
|
|
150
|
+
check_if_already_connected: bool | None = True
|
|
151
|
+
):
|
|
152
|
+
self.username = username
|
|
153
|
+
self.hostname = hostname
|
|
154
|
+
self.port = port
|
|
155
|
+
self.password = password
|
|
156
|
+
|
|
157
|
+
if base_timeout is None:
|
|
158
|
+
base_timeout = timedelta(seconds=10).total_seconds()
|
|
159
|
+
self.base_timeout = base_timeout
|
|
160
|
+
|
|
161
|
+
if check_if_already_connected is None:
|
|
162
|
+
check_if_already_connected = True
|
|
163
|
+
self.check_if_already_connected = check_if_already_connected
|
|
164
|
+
|
|
165
|
+
self._logger = logging.getLogger(
|
|
166
|
+
f"{logging.getLogger(self.__class__.__name__)} - {self.username}@{self.hostname}:{self.port}"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
self.async_conn: asyncssh.SSHClientConnection | None = None
|
|
170
|
+
|
|
171
|
+
self.sync_client: paramiko.SSHClient | None = None
|
|
172
|
+
|
|
173
|
+
"""SYNC"""
|
|
174
|
+
|
|
175
|
+
def sync_connect(
|
|
176
|
+
self,
|
|
177
|
+
*,
|
|
178
|
+
common_timeout: float | None = None,
|
|
179
|
+
connect_kwargs: dict[str, Any] | None = None,
|
|
180
|
+
check_if_already_connected: bool | None = None
|
|
181
|
+
) -> SSHRunner:
|
|
182
|
+
if check_if_already_connected is None:
|
|
183
|
+
check_if_already_connected = self.check_if_already_connected
|
|
184
|
+
|
|
185
|
+
if check_if_already_connected and self.sync_client is not None:
|
|
186
|
+
self._logger.info("already connected")
|
|
187
|
+
return self
|
|
188
|
+
|
|
189
|
+
if connect_kwargs is None:
|
|
190
|
+
connect_kwargs = {}
|
|
191
|
+
if common_timeout is None:
|
|
192
|
+
common_timeout = self.base_timeout
|
|
193
|
+
|
|
194
|
+
connect_kwargs["hostname"] = self.hostname
|
|
195
|
+
connect_kwargs["username"] = self.username
|
|
196
|
+
connect_kwargs["password"] = self.password
|
|
197
|
+
connect_kwargs["port"] = self.port
|
|
198
|
+
|
|
199
|
+
if connect_kwargs.get("timeout") is None:
|
|
200
|
+
connect_kwargs["timeout"] = common_timeout
|
|
201
|
+
if connect_kwargs.get("auth_timeout") is None:
|
|
202
|
+
connect_kwargs["auth_timeout"] = common_timeout
|
|
203
|
+
if connect_kwargs.get("banner_timeout") is None:
|
|
204
|
+
connect_kwargs["banner_timeout"] = common_timeout
|
|
205
|
+
if connect_kwargs.get("channel_timeout") is None:
|
|
206
|
+
connect_kwargs["channel_timeout"] = common_timeout
|
|
207
|
+
|
|
208
|
+
self._logger.info("connecting")
|
|
209
|
+
|
|
210
|
+
self.sync_client = paramiko.SSHClient()
|
|
211
|
+
self.sync_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
self.sync_client.connect(**connect_kwargs)
|
|
215
|
+
except Exception as exception:
|
|
216
|
+
self._logger.error(f"not connected, {exception=}", exc_info=True)
|
|
217
|
+
raise ConnectionSSHException(ssh_runner=self, base_exception=exception)
|
|
218
|
+
|
|
219
|
+
self._logger.info("connected")
|
|
220
|
+
|
|
221
|
+
return self
|
|
222
|
+
|
|
223
|
+
def sync_check_connection(self):
|
|
224
|
+
self.sync_connect()
|
|
225
|
+
|
|
226
|
+
def sync_is_conn_good(self) -> bool:
|
|
227
|
+
try:
|
|
228
|
+
self.sync_check_connection()
|
|
229
|
+
except ConnectionSSHException:
|
|
230
|
+
return False
|
|
231
|
+
except Exception:
|
|
232
|
+
return False
|
|
233
|
+
return True
|
|
234
|
+
|
|
235
|
+
def sync_run(
|
|
236
|
+
self,
|
|
237
|
+
command: str,
|
|
238
|
+
*,
|
|
239
|
+
timeout: float | None = timedelta(seconds=10).total_seconds(),
|
|
240
|
+
raise_for_bad_return_code: bool = True
|
|
241
|
+
) -> SSHRunResult:
|
|
242
|
+
if not command or not command.strip():
|
|
243
|
+
raise ValueError("command must be a non-empty string")
|
|
244
|
+
|
|
245
|
+
if timeout is None:
|
|
246
|
+
timeout = self.base_timeout
|
|
247
|
+
|
|
248
|
+
self.sync_connect()
|
|
249
|
+
|
|
250
|
+
self._logger.info(command)
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
stdin, stdout, stderr = self.sync_client.exec_command(
|
|
254
|
+
command=command,
|
|
255
|
+
timeout=timeout
|
|
256
|
+
)
|
|
257
|
+
return_code = stdout.channel.recv_exit_status()
|
|
258
|
+
stdout = stdout.read().decode()
|
|
259
|
+
stderr = stderr.read().decode()
|
|
260
|
+
except Exception as exception:
|
|
261
|
+
raise ErrorInRunSSHException(ssh_runner=self, base_exception=exception)
|
|
262
|
+
|
|
263
|
+
ssh_run_result = SSHRunResult(
|
|
264
|
+
out=stdout,
|
|
265
|
+
err=stderr,
|
|
266
|
+
return_code=return_code,
|
|
267
|
+
ssh_runner=self
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
if raise_for_bad_return_code:
|
|
271
|
+
ssh_run_result.raise_for_bad_return_code()
|
|
272
|
+
|
|
273
|
+
return ssh_run_result
|
|
274
|
+
|
|
275
|
+
def sync_close(self):
|
|
276
|
+
if self.sync_client is not None:
|
|
277
|
+
self.sync_client.close()
|
|
278
|
+
self.sync_client = paramiko.SSHClient()
|
|
279
|
+
self.sync_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
280
|
+
self.sync_client = None
|
|
281
|
+
|
|
282
|
+
"""ASYNC SYNC"""
|
|
283
|
+
|
|
284
|
+
async def async_connect(
|
|
285
|
+
self,
|
|
286
|
+
*,
|
|
287
|
+
common_timeout: float | None = None,
|
|
288
|
+
connect_kwargs: dict[str, Any] | None = None,
|
|
289
|
+
check_if_already_connected: bool | None = None
|
|
290
|
+
) -> SSHRunner:
|
|
291
|
+
if check_if_already_connected is None:
|
|
292
|
+
check_if_already_connected = self.check_if_already_connected
|
|
293
|
+
|
|
294
|
+
if check_if_already_connected and self.async_conn is not None:
|
|
295
|
+
self._logger.info("already connected")
|
|
296
|
+
return self
|
|
297
|
+
|
|
298
|
+
if connect_kwargs is None:
|
|
299
|
+
connect_kwargs = {}
|
|
300
|
+
if common_timeout is None:
|
|
301
|
+
common_timeout = self.base_timeout
|
|
302
|
+
|
|
303
|
+
connect_kwargs["host"] = self.hostname
|
|
304
|
+
connect_kwargs["username"] = self.username
|
|
305
|
+
connect_kwargs["password"] = self.password
|
|
306
|
+
connect_kwargs["port"] = self.port
|
|
307
|
+
|
|
308
|
+
if connect_kwargs.get("connect_timeout") is None:
|
|
309
|
+
connect_kwargs["connect_timeout"] = common_timeout
|
|
310
|
+
if connect_kwargs.get("login_timeout") is None:
|
|
311
|
+
connect_kwargs["login_timeout"] = common_timeout
|
|
312
|
+
|
|
313
|
+
connect_kwargs["known_hosts"] = None
|
|
314
|
+
|
|
315
|
+
self._logger.info("connecting")
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
self.async_conn = await asyncssh.connect(**connect_kwargs)
|
|
319
|
+
except Exception as exception:
|
|
320
|
+
self._logger.error(f"not connected, {exception=}", exc_info=True)
|
|
321
|
+
raise ConnectionSSHException(ssh_runner=self, base_exception=exception)
|
|
322
|
+
|
|
323
|
+
self._logger.info("connected")
|
|
324
|
+
|
|
325
|
+
return self
|
|
326
|
+
|
|
327
|
+
async def async_check_connection(self):
|
|
328
|
+
await self.async_connect()
|
|
329
|
+
|
|
330
|
+
async def async_is_conn_good(self) -> bool:
|
|
331
|
+
try:
|
|
332
|
+
await self.async_check_connection()
|
|
333
|
+
except ConnectionSSHException:
|
|
334
|
+
return False
|
|
335
|
+
return True
|
|
336
|
+
|
|
337
|
+
async def async_run(
|
|
338
|
+
self,
|
|
339
|
+
command: str,
|
|
340
|
+
*,
|
|
341
|
+
timeout: float | None = timedelta(seconds=10).total_seconds(),
|
|
342
|
+
raise_for_bad_return_code: bool = True
|
|
343
|
+
) -> SSHRunResult:
|
|
344
|
+
if not command or not command.strip():
|
|
345
|
+
raise ValueError("command must be a non-empty string")
|
|
346
|
+
|
|
347
|
+
if timeout is None:
|
|
348
|
+
timeout = self.base_timeout
|
|
349
|
+
|
|
350
|
+
await self.async_connect()
|
|
351
|
+
|
|
352
|
+
self._logger.info(command)
|
|
353
|
+
|
|
354
|
+
try:
|
|
355
|
+
result: asyncssh.SSHCompletedProcess = await self.async_conn.run(
|
|
356
|
+
command,
|
|
357
|
+
check=False,
|
|
358
|
+
timeout=timeout
|
|
359
|
+
)
|
|
360
|
+
except Exception as exception:
|
|
361
|
+
raise ErrorInRunSSHException(ssh_runner=self, base_exception=exception)
|
|
362
|
+
|
|
363
|
+
ssh_run_result = SSHRunResult(
|
|
364
|
+
out=result.stdout,
|
|
365
|
+
err=result.stderr,
|
|
366
|
+
return_code=result.returncode,
|
|
367
|
+
ssh_runner=self
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
if raise_for_bad_return_code:
|
|
371
|
+
ssh_run_result.raise_for_bad_return_code()
|
|
372
|
+
|
|
373
|
+
return ssh_run_result
|
|
374
|
+
|
|
375
|
+
async def async_close(self):
|
|
376
|
+
if self.async_conn is not None:
|
|
377
|
+
self.async_conn.close()
|
|
378
|
+
self.async_conn = None
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
def __example():
|
|
382
|
+
pass
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
async def __async_example():
|
|
386
|
+
pass
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
if __name__ == '__main__':
|
|
390
|
+
__example()
|
|
391
|
+
asyncio.run(__async_example())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arpakitlib
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.336
|
|
4
4
|
Summary: arpakitlib
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -24,6 +24,7 @@ Requires-Dist: aiosmtplib (>=4.0.0,<5.0.0)
|
|
|
24
24
|
Requires-Dist: aiosqlite (>=0.21.0,<0.22.0)
|
|
25
25
|
Requires-Dist: alembic (>=1.14.1,<2.0.0)
|
|
26
26
|
Requires-Dist: asyncpg (>=0.30.0,<0.31.0)
|
|
27
|
+
Requires-Dist: asyncssh (>=2.21.1,<3.0.0)
|
|
27
28
|
Requires-Dist: bs4 (>=0.0.2,<0.0.3)
|
|
28
29
|
Requires-Dist: cachetools (>=5.5.1,<6.0.0)
|
|
29
30
|
Requires-Dist: celery (>=5.4.0,<6.0.0)
|
|
@@ -33,9 +34,11 @@ Requires-Dist: fastapi (>=0.118.0,<0.119.0)
|
|
|
33
34
|
Requires-Dist: gunicorn (>=23.0.0,<24.0.0)
|
|
34
35
|
Requires-Dist: itsdangerous (>=2.2.0,<3.0.0)
|
|
35
36
|
Requires-Dist: markdown (>=3.7,<4.0)
|
|
37
|
+
Requires-Dist: openai (>=2.6.1,<3.0.0)
|
|
36
38
|
Requires-Dist: openpyxl (>=3.1.5,<4.0.0)
|
|
37
39
|
Requires-Dist: orjson (>=3.10.15,<4.0.0)
|
|
38
40
|
Requires-Dist: pandas (>=2.2.3,<3.0.0)
|
|
41
|
+
Requires-Dist: paramiko (>=4.0.0,<5.0.0)
|
|
39
42
|
Requires-Dist: pika (>=1.3.2,<2.0.0)
|
|
40
43
|
Requires-Dist: poetry (>=2.0.1,<3.0.0)
|
|
41
44
|
Requires-Dist: poetry-plugin-export (>=1.9.0)
|
|
@@ -61,8 +64,9 @@ Requires-Dist: xlrd (>=2.0.1,<3.0.0)
|
|
|
61
64
|
Requires-Dist: xlsxwriter (>=3.2.5,<4.0.0)
|
|
62
65
|
Project-URL: Documentation, https://github.com/ARPAKIT-Company/arpakitlib
|
|
63
66
|
Project-URL: Homepage, https://github.com/ARPAKIT-Company/arpakitlib
|
|
64
|
-
Project-URL: Repository, https://github.com/ARPAKIT-Company/arpakitlib
|
|
65
67
|
Project-URL: arpakit_company_site, https://arpakit.com
|
|
68
|
+
Project-URL: arpakit_company_support_tg, https://t.me/arpakit_company_support
|
|
69
|
+
Project-URL: git_repository, https://github.com/ARPAKIT-Company/arpakitlib
|
|
66
70
|
Project-URL: telegram_channel, https://t.me/arpakitlib
|
|
67
71
|
Description-Content-Type: text/markdown
|
|
68
72
|
|
|
@@ -425,12 +425,13 @@ arpakitlib/ar_sqladmin_util.py,sha256=SEoaowAPF3lhxPsNjwmOymNJ55Ty9rmzvsDm7gD5Ce
|
|
|
425
425
|
arpakitlib/ar_sqlalchemy_drop_check_constraints_util.py,sha256=uVktYLjNHrMPWQAq8eBpapShPKbLb3LrRBnnss3gaYY,3624
|
|
426
426
|
arpakitlib/ar_sqlalchemy_ensure_check_constraints_util.py,sha256=gqZTPSCAPUMRiXcmv9xls5S8YkUAg-gwFIEvqQsJ_JM,5437
|
|
427
427
|
arpakitlib/ar_sqlalchemy_util.py,sha256=hnwZW7v8FOjzYCkaoumkZYrtDkAv2gev9ZWH6it8H5k,16050
|
|
428
|
+
arpakitlib/ar_ssh_runner_util.py,sha256=3gKtLXf77AsYVZ7CSE4zhHJZPCoEUlQS5lwWytgN5e0,11844
|
|
428
429
|
arpakitlib/ar_str_util.py,sha256=6KlLL-SB8gzK-6gwQEd3zuYbRvtjd9HFpJ9-xHbkH6U,4355
|
|
429
430
|
arpakitlib/ar_type_util.py,sha256=Cs_tef-Fc5xeyAF54KgISCsP11NHyzIsglm4S3Xx7iM,4049
|
|
430
431
|
arpakitlib/ar_uppercase_env_keys_util.py,sha256=BsUCJhfchBIav0AE54_tVgYcE4p1JYoWdPGCHWZnROA,2790
|
|
431
432
|
arpakitlib/ar_yookassa_api_client_util.py,sha256=7DL_0GyIOTuSNBHUO3qWxAXMKlBRHjKgcA6ttst8k1A,5265
|
|
432
|
-
arpakitlib-1.8.
|
|
433
|
-
arpakitlib-1.8.
|
|
434
|
-
arpakitlib-1.8.
|
|
435
|
-
arpakitlib-1.8.
|
|
436
|
-
arpakitlib-1.8.
|
|
433
|
+
arpakitlib-1.8.336.dist-info/METADATA,sha256=_Hl-w75g6orlNEpZqp8ej_NI-H-_OonwSHBCQHmzKEw,4008
|
|
434
|
+
arpakitlib-1.8.336.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
435
|
+
arpakitlib-1.8.336.dist-info/entry_points.txt,sha256=36xqR3PJFT2kuwjkM_EqoIy0qFUDPKSm_mJaI7emewE,87
|
|
436
|
+
arpakitlib-1.8.336.dist-info/licenses/LICENSE,sha256=GPEDQMam2r7FSTYqM1mm7aKnxLaWcBotH7UvQtea-ec,11355
|
|
437
|
+
arpakitlib-1.8.336.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|