embed-client 1.0.1.1__py3-none-any.whl → 3.1.0.0__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.
@@ -0,0 +1,466 @@
1
+ """
2
+ SSL/TLS Manager for embed-client.
3
+
4
+ This module provides SSL/TLS management for the embed-client, supporting
5
+ all security modes including HTTP, HTTPS, and mTLS. It integrates with
6
+ mcp_security_framework when available and provides fallback implementations.
7
+
8
+ Author: Vasiliy Zdanovskiy
9
+ email: vasilyvz@gmail.com
10
+ """
11
+
12
+ import logging
13
+ import ssl
14
+ import os
15
+ from pathlib import Path
16
+ from typing import Any, Dict, List, Optional, Union
17
+
18
+ # Try to import mcp_security_framework components
19
+ try:
20
+ from mcp_security_framework.core.ssl_manager import SSLManager as FrameworkSSLManager
21
+ from mcp_security_framework.core.cert_manager import CertificateManager
22
+ from mcp_security_framework.schemas.config import SSLConfig
23
+ from mcp_security_framework.schemas.models import CertificateInfo
24
+ from mcp_security_framework.utils.cert_utils import (
25
+ extract_certificate_info,
26
+ get_certificate_expiry,
27
+ is_certificate_self_signed,
28
+ parse_certificate,
29
+ validate_certificate_chain
30
+ )
31
+ SECURITY_FRAMEWORK_AVAILABLE = True
32
+ except ImportError:
33
+ SECURITY_FRAMEWORK_AVAILABLE = False
34
+ print("Warning: mcp_security_framework not available. Using fallback SSL/TLS management.")
35
+
36
+ # Fallback certificate handling
37
+ try:
38
+ from cryptography import x509
39
+ from cryptography.hazmat.primitives import serialization
40
+ CRYPTOGRAPHY_AVAILABLE = True
41
+ except ImportError:
42
+ CRYPTOGRAPHY_AVAILABLE = False
43
+ print("Warning: cryptography not available. Limited SSL/TLS functionality.")
44
+
45
+
46
+ class SSLManagerError(Exception):
47
+ """Raised when SSL/TLS operations fail."""
48
+
49
+ def __init__(self, message: str, error_code: int = -32002):
50
+ self.message = message
51
+ self.error_code = error_code
52
+ super().__init__(self.message)
53
+
54
+
55
+ class ClientSSLManager:
56
+ """
57
+ Client SSL/TLS Manager.
58
+
59
+ This class provides SSL/TLS management for the embed-client,
60
+ supporting all security modes with integration to
61
+ mcp_security_framework when available.
62
+ """
63
+
64
+ def __init__(self, config: Dict[str, Any]):
65
+ """
66
+ Initialize SSL/TLS manager.
67
+
68
+ Args:
69
+ config: SSL/TLS configuration dictionary
70
+ """
71
+ self.config = config
72
+ self.logger = logging.getLogger(__name__)
73
+
74
+ # Initialize security framework components if available
75
+ self.ssl_manager = None
76
+ self.cert_manager = None
77
+
78
+ if SECURITY_FRAMEWORK_AVAILABLE:
79
+ self._initialize_security_framework()
80
+ else:
81
+ self.logger.warning("mcp_security_framework not available, using fallback SSL/TLS management")
82
+
83
+ def _initialize_security_framework(self) -> None:
84
+ """Initialize mcp_security_framework components."""
85
+ try:
86
+ # Create SSL config
87
+ ssl_config = SSLConfig(
88
+ enabled=self.config.get("ssl", {}).get("enabled", False),
89
+ cert_file=self.config.get("ssl", {}).get("cert_file"),
90
+ key_file=self.config.get("ssl", {}).get("key_file"),
91
+ ca_cert_file=self.config.get("ssl", {}).get("ca_cert_file"),
92
+ verify_mode=self.config.get("ssl", {}).get("verify_mode", "CERT_REQUIRED"),
93
+ check_hostname=self.config.get("ssl", {}).get("check_hostname", True),
94
+ check_expiry=self.config.get("ssl", {}).get("check_expiry", True)
95
+ )
96
+
97
+ # Initialize managers
98
+ self.ssl_manager = FrameworkSSLManager(ssl_config)
99
+
100
+ self.logger.info("Security framework SSL/TLS components initialized successfully")
101
+
102
+ except Exception as e:
103
+ self.logger.warning(f"Failed to initialize security framework SSL/TLS: {e}")
104
+ self.ssl_manager = None
105
+ self.cert_manager = None
106
+
107
+ def create_client_ssl_context(self) -> Optional[ssl.SSLContext]:
108
+ """
109
+ Create SSL context for client connections.
110
+
111
+ Returns:
112
+ SSL context for client connections or None if SSL is disabled
113
+ """
114
+ # Check if SSL is enabled first
115
+ if not self.is_ssl_enabled():
116
+ return None
117
+
118
+ try:
119
+ # Force fallback implementation for CERT_NONE mode
120
+ ssl_config = self.config.get("ssl", {})
121
+ if ssl_config.get("verify_mode") == "CERT_NONE":
122
+ return self._create_client_ssl_context_fallback()
123
+ elif self.ssl_manager:
124
+ # Use security framework
125
+ return self.ssl_manager.create_client_context()
126
+ else:
127
+ # Fallback implementation
128
+ return self._create_client_ssl_context_fallback()
129
+
130
+ except Exception as e:
131
+ self.logger.error(f"Failed to create client SSL context: {e}")
132
+ raise SSLManagerError(f"Failed to create client SSL context: {e}")
133
+
134
+ def _create_client_ssl_context_fallback(self) -> Optional[ssl.SSLContext]:
135
+ """Fallback SSL context creation."""
136
+ ssl_config = self.config.get("ssl", {})
137
+
138
+ if not ssl_config.get("enabled", False):
139
+ return None
140
+
141
+ try:
142
+ # Configure verification
143
+ verify_mode = ssl_config.get("verify_mode", "CERT_REQUIRED")
144
+ check_hostname = ssl_config.get("check_hostname", True)
145
+
146
+ # Force check_hostname=False for CERT_NONE mode
147
+ if verify_mode == "CERT_NONE":
148
+ check_hostname = False
149
+
150
+ # Create SSL context based on verification mode
151
+ if verify_mode == "CERT_NONE":
152
+ context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
153
+ context.check_hostname = False
154
+ context.verify_mode = ssl.CERT_NONE
155
+ else:
156
+ context = ssl.create_default_context()
157
+ context.check_hostname = check_hostname
158
+ if verify_mode == "CERT_OPTIONAL":
159
+ context.verify_mode = ssl.CERT_OPTIONAL
160
+ else: # CERT_REQUIRED
161
+ context.verify_mode = ssl.CERT_REQUIRED
162
+
163
+ # Load CA certificate if provided
164
+ ca_cert_file = ssl_config.get("ca_cert_file")
165
+ if ca_cert_file and os.path.exists(ca_cert_file):
166
+ context.load_verify_locations(ca_cert_file)
167
+
168
+ # Load client certificate if provided (for mTLS)
169
+ cert_file = ssl_config.get("cert_file")
170
+ key_file = ssl_config.get("key_file")
171
+ if cert_file and key_file and os.path.exists(cert_file) and os.path.exists(key_file):
172
+ context.load_cert_chain(cert_file, key_file)
173
+
174
+ return context
175
+
176
+ except Exception as e:
177
+ raise SSLManagerError(f"Failed to create SSL context: {e}")
178
+
179
+ def create_connector(self) -> Optional[Any]:
180
+ """
181
+ Create aiohttp connector with SSL context.
182
+
183
+ Returns:
184
+ aiohttp connector with SSL context or None if SSL is disabled
185
+ """
186
+ try:
187
+ import aiohttp
188
+
189
+ ssl_context = self.create_client_ssl_context()
190
+ if ssl_context is None:
191
+ return None
192
+
193
+ return aiohttp.TCPConnector(ssl=ssl_context)
194
+
195
+ except ImportError:
196
+ self.logger.error("aiohttp not available for connector creation")
197
+ return None
198
+ except Exception as e:
199
+ self.logger.error(f"Failed to create connector: {e}")
200
+ return None
201
+
202
+ def validate_certificate(self, cert_file: str) -> Dict[str, Any]:
203
+ """
204
+ Validate certificate file.
205
+
206
+ Args:
207
+ cert_file: Path to certificate file
208
+
209
+ Returns:
210
+ Dictionary with validation results
211
+ """
212
+ try:
213
+ if self.ssl_manager and CRYPTOGRAPHY_AVAILABLE:
214
+ # Use security framework
215
+ cert_info = extract_certificate_info(cert_file)
216
+ return {
217
+ "valid": True,
218
+ "subject": str(cert_info.subject),
219
+ "issuer": str(cert_info.issuer),
220
+ "not_valid_before": cert_info.not_valid_before.isoformat(),
221
+ "not_valid_after": cert_info.not_valid_after.isoformat(),
222
+ "serial_number": str(cert_info.serial_number),
223
+ "is_self_signed": is_certificate_self_signed(cert_file)
224
+ }
225
+ else:
226
+ # Fallback implementation
227
+ return self._validate_certificate_fallback(cert_file)
228
+
229
+ except Exception as e:
230
+ self.logger.error(f"Certificate validation failed: {e}")
231
+ return {
232
+ "valid": False,
233
+ "error": str(e)
234
+ }
235
+
236
+ def _validate_certificate_fallback(self, cert_file: str) -> Dict[str, Any]:
237
+ """Fallback certificate validation."""
238
+ if not CRYPTOGRAPHY_AVAILABLE:
239
+ return {
240
+ "valid": False,
241
+ "error": "cryptography not available for certificate validation"
242
+ }
243
+
244
+ try:
245
+ if not os.path.exists(cert_file):
246
+ return {
247
+ "valid": False,
248
+ "error": "Certificate file not found"
249
+ }
250
+
251
+ # Basic file existence and readability check
252
+ with open(cert_file, 'rb') as f:
253
+ cert_data = f.read()
254
+
255
+ # Try to parse certificate
256
+ cert = x509.load_pem_x509_certificate(cert_data)
257
+
258
+ return {
259
+ "valid": True,
260
+ "subject": str(cert.subject),
261
+ "issuer": str(cert.issuer),
262
+ "not_valid_before": cert.not_valid_before.isoformat(),
263
+ "not_valid_after": cert.not_valid_after.isoformat(),
264
+ "serial_number": str(cert.serial_number)
265
+ }
266
+
267
+ except Exception as e:
268
+ return {
269
+ "valid": False,
270
+ "error": f"Certificate validation failed: {e}"
271
+ }
272
+
273
+ def get_ssl_config(self) -> Dict[str, Any]:
274
+ """
275
+ Get current SSL configuration.
276
+
277
+ Returns:
278
+ Dictionary with SSL configuration
279
+ """
280
+ return self.config.get("ssl", {})
281
+
282
+ def is_ssl_enabled(self) -> bool:
283
+ """
284
+ Check if SSL/TLS is enabled.
285
+
286
+ Returns:
287
+ True if SSL/TLS is enabled, False otherwise
288
+ """
289
+ return self.config.get("ssl", {}).get("enabled", False)
290
+
291
+ def is_mtls_enabled(self) -> bool:
292
+ """
293
+ Check if mTLS (mutual TLS) is enabled.
294
+
295
+ Returns:
296
+ True if mTLS is enabled, False otherwise
297
+ """
298
+ ssl_config = self.config.get("ssl", {})
299
+ return (ssl_config.get("enabled", False) and
300
+ bool(ssl_config.get("cert_file")) and
301
+ bool(ssl_config.get("key_file")))
302
+
303
+ def get_certificate_info(self, cert_file: str) -> Optional[Dict[str, Any]]:
304
+ """
305
+ Get certificate information.
306
+
307
+ Args:
308
+ cert_file: Path to certificate file
309
+
310
+ Returns:
311
+ Dictionary with certificate information or None if not available
312
+ """
313
+ try:
314
+ if self.ssl_manager and CRYPTOGRAPHY_AVAILABLE:
315
+ # Use security framework
316
+ cert_info = extract_certificate_info(cert_file)
317
+ return {
318
+ "subject": str(cert_info.subject),
319
+ "issuer": str(cert_info.issuer),
320
+ "not_valid_before": cert_info.not_valid_before.isoformat(),
321
+ "not_valid_after": cert_info.not_valid_after.isoformat(),
322
+ "serial_number": str(cert_info.serial_number),
323
+ "version": cert_info.version,
324
+ "signature_algorithm": str(cert_info.signature_algorithm_oid),
325
+ "is_self_signed": is_certificate_self_signed(cert_file)
326
+ }
327
+ else:
328
+ # Fallback implementation
329
+ return self._get_certificate_info_fallback(cert_file)
330
+
331
+ except Exception as e:
332
+ self.logger.error(f"Failed to get certificate info: {e}")
333
+ return None
334
+
335
+ def _get_certificate_info_fallback(self, cert_file: str) -> Optional[Dict[str, Any]]:
336
+ """Fallback certificate info extraction."""
337
+ if not CRYPTOGRAPHY_AVAILABLE:
338
+ return None
339
+
340
+ try:
341
+ if not os.path.exists(cert_file):
342
+ return None
343
+
344
+ with open(cert_file, 'rb') as f:
345
+ cert_data = f.read()
346
+
347
+ cert = x509.load_pem_x509_certificate(cert_data)
348
+
349
+ return {
350
+ "subject": str(cert.subject),
351
+ "issuer": str(cert.issuer),
352
+ "not_valid_before": cert.not_valid_before.isoformat(),
353
+ "not_valid_after": cert.not_valid_after.isoformat(),
354
+ "serial_number": str(cert.serial_number),
355
+ "version": cert.version.name,
356
+ "signature_algorithm": str(cert.signature_algorithm_oid)
357
+ }
358
+
359
+ except Exception as e:
360
+ self.logger.error(f"Failed to extract certificate info: {e}")
361
+ return None
362
+
363
+ def validate_ssl_config(self) -> List[str]:
364
+ """
365
+ Validate SSL configuration.
366
+
367
+ Returns:
368
+ List of validation errors
369
+ """
370
+ errors = []
371
+ ssl_config = self.config.get("ssl", {})
372
+
373
+ if not ssl_config.get("enabled", False):
374
+ return errors # SSL disabled, no validation needed
375
+
376
+ # Check certificate files if mTLS is configured
377
+ cert_file = ssl_config.get("cert_file")
378
+ key_file = ssl_config.get("key_file")
379
+
380
+ if cert_file and not os.path.exists(cert_file):
381
+ errors.append(f"Certificate file not found: {cert_file}")
382
+
383
+ if key_file and not os.path.exists(key_file):
384
+ errors.append(f"Key file not found: {key_file}")
385
+
386
+ # Check CA certificate if provided
387
+ ca_cert_file = ssl_config.get("ca_cert_file")
388
+ if ca_cert_file and not os.path.exists(ca_cert_file):
389
+ errors.append(f"CA certificate file not found: {ca_cert_file}")
390
+
391
+ # Validate certificate if provided
392
+ if cert_file and os.path.exists(cert_file):
393
+ validation_result = self.validate_certificate(cert_file)
394
+ if not validation_result.get("valid", False):
395
+ errors.append(f"Certificate validation failed: {validation_result.get('error', 'Unknown error')}")
396
+
397
+ return errors
398
+
399
+ def get_supported_protocols(self) -> List[str]:
400
+ """
401
+ Get list of supported SSL/TLS protocols.
402
+
403
+ Returns:
404
+ List of supported protocol names
405
+ """
406
+ protocols = []
407
+
408
+ try:
409
+ # Check available protocols
410
+ if hasattr(ssl, 'PROTOCOL_TLSv1_2'):
411
+ protocols.append("TLSv1.2")
412
+ if hasattr(ssl, 'PROTOCOL_TLSv1_3'):
413
+ protocols.append("TLSv1.3")
414
+ if hasattr(ssl, 'PROTOCOL_TLS'):
415
+ protocols.append("TLS")
416
+
417
+ # Fallback to basic protocols
418
+ if not protocols:
419
+ protocols = ["SSLv23", "TLS"]
420
+
421
+ except Exception as e:
422
+ self.logger.warning(f"Failed to detect supported protocols: {e}")
423
+ protocols = ["TLS"] # Basic fallback
424
+
425
+ return protocols
426
+
427
+
428
+ def create_ssl_manager(config: Dict[str, Any]) -> ClientSSLManager:
429
+ """
430
+ Create SSL/TLS manager from configuration.
431
+
432
+ Args:
433
+ config: Configuration dictionary
434
+
435
+ Returns:
436
+ ClientSSLManager instance
437
+ """
438
+ return ClientSSLManager(config)
439
+
440
+
441
+ def create_ssl_context(config: Dict[str, Any]) -> Optional[ssl.SSLContext]:
442
+ """
443
+ Create SSL context from configuration.
444
+
445
+ Args:
446
+ config: Configuration dictionary
447
+
448
+ Returns:
449
+ SSL context or None if SSL is disabled
450
+ """
451
+ ssl_manager = ClientSSLManager(config)
452
+ return ssl_manager.create_client_ssl_context()
453
+
454
+
455
+ def create_connector(config: Dict[str, Any]) -> Optional[Any]:
456
+ """
457
+ Create aiohttp connector from configuration.
458
+
459
+ Args:
460
+ config: Configuration dictionary
461
+
462
+ Returns:
463
+ aiohttp connector or None if SSL is disabled
464
+ """
465
+ ssl_manager = ClientSSLManager(config)
466
+ return ssl_manager.create_connector()
@@ -0,0 +1,229 @@
1
+ Metadata-Version: 2.4
2
+ Name: embed-client
3
+ Version: 3.1.0.0
4
+ Summary: Async client for Embedding Service API with comprehensive authentication, SSL/TLS, and mTLS support
5
+ Author-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: aiohttp
9
+ Requires-Dist: PyJWT>=2.0.0
10
+ Requires-Dist: cryptography>=3.0.0
11
+ Requires-Dist: pydantic>=2.0.0
12
+ Provides-Extra: test
13
+ Requires-Dist: pytest; extra == "test"
14
+ Requires-Dist: pytest-asyncio; extra == "test"
15
+
16
+ # embed-client
17
+
18
+ Асинхронный клиент для Embedding Service API с поддержкой всех режимов безопасности.
19
+
20
+ ## Возможности
21
+
22
+ - ✅ **Асинхронный API** - полная поддержка async/await
23
+ - ✅ **Все режимы безопасности** - HTTP, HTTPS, mTLS
24
+ - ✅ **Аутентификация** - API Key, JWT, Basic Auth, Certificate
25
+ - ✅ **SSL/TLS поддержка** - полная интеграция с mcp_security_framework
26
+ - ✅ **Конфигурация** - файлы конфигурации, переменные окружения, аргументы
27
+ - ✅ **Обратная совместимость** - API формат не изменился, добавлена только безопасность
28
+ - ✅ **Типизация** - 100% type-annotated код
29
+ - ✅ **Тестирование** - 84+ тестов с полным покрытием
30
+
31
+ ## Quick Start: Примеры запуска
32
+
33
+ ### Базовое использование
34
+
35
+ **Вариант 1: через аргументы командной строки**
36
+
37
+ ```sh
38
+ # HTTP без аутентификации
39
+ python embed_client/example_async_usage.py --base-url http://localhost --port 8001
40
+
41
+ # HTTP с API ключом
42
+ python embed_client/example_async_usage.py --base-url http://localhost --port 8001 \
43
+ --auth-method api_key --api-key admin_key_123
44
+
45
+ # HTTPS с SSL
46
+ python embed_client/example_async_usage.py --base-url https://localhost --port 9443 \
47
+ --ssl-verify-mode CERT_REQUIRED
48
+
49
+ # mTLS с сертификатами
50
+ python embed_client/example_async_usage.py --base-url https://localhost --port 9443 \
51
+ --cert-file certs/client.crt --key-file keys/client.key --ca-cert-file certs/ca.crt
52
+ ```
53
+
54
+ **Вариант 2: через переменные окружения**
55
+
56
+ ```sh
57
+ export EMBED_CLIENT_BASE_URL=http://localhost
58
+ export EMBED_CLIENT_PORT=8001
59
+ export EMBED_CLIENT_AUTH_METHOD=api_key
60
+ export EMBED_CLIENT_API_KEY=admin_key_123
61
+ python embed_client/example_async_usage.py
62
+ ```
63
+
64
+ **Вариант 3: через файл конфигурации**
65
+
66
+ ```sh
67
+ python embed_client/example_async_usage.py --config configs/https_token.json
68
+ ```
69
+
70
+ ### Режимы безопасности
71
+
72
+ #### 1. HTTP (без аутентификации)
73
+ ```python
74
+ from embed_client.async_client import EmbeddingServiceAsyncClient
75
+
76
+ client = EmbeddingServiceAsyncClient(
77
+ base_url="http://localhost",
78
+ port=8001
79
+ )
80
+ ```
81
+
82
+ #### 2. HTTP + Token
83
+ ```python
84
+ from embed_client.config import ClientConfig
85
+
86
+ # API Key
87
+ config = ClientConfig.create_http_token_config(
88
+ "http://localhost", 8001, {"user": "api_key_123"}
89
+ )
90
+
91
+ # JWT
92
+ config = ClientConfig.create_http_jwt_config(
93
+ "http://localhost", 8001, "secret", "username", "password"
94
+ )
95
+
96
+ # Basic Auth
97
+ config = ClientConfig.create_http_basic_config(
98
+ "http://localhost", 8001, "username", "password"
99
+ )
100
+ ```
101
+
102
+ #### 3. HTTPS
103
+ ```python
104
+ config = ClientConfig.create_https_config(
105
+ "https://localhost", 9443,
106
+ ca_cert_file="certs/ca.crt"
107
+ )
108
+ ```
109
+
110
+ #### 4. mTLS (взаимная аутентификация)
111
+ ```python
112
+ config = ClientConfig.create_mtls_config(
113
+ "https://localhost", 9443,
114
+ cert_file="certs/client.crt",
115
+ key_file="keys/client.key",
116
+ ca_cert_file="certs/ca.crt"
117
+ )
118
+ ```
119
+
120
+ ### Программное использование
121
+
122
+ ```python
123
+ import asyncio
124
+ from embed_client.async_client import EmbeddingServiceAsyncClient
125
+ from embed_client.config import ClientConfig
126
+
127
+ async def main():
128
+ # Создание конфигурации
129
+ config = ClientConfig.create_http_token_config(
130
+ "http://localhost", 8001, {"user": "api_key_123"}
131
+ )
132
+
133
+ # Использование клиента
134
+ async with EmbeddingServiceAsyncClient.from_config(config) as client:
135
+ # Проверка статуса
136
+ print(f"Аутентификация: {client.get_auth_method()}")
137
+ print(f"SSL включен: {client.is_ssl_enabled()}")
138
+ print(f"mTLS включен: {client.is_mtls_enabled()}")
139
+
140
+ # Выполнение запроса
141
+ result = await client.cmd("embed", params={"texts": ["hello world"]})
142
+
143
+ # Извлечение данных
144
+ embeddings = client.extract_embeddings(result)
145
+ texts = client.extract_texts(result)
146
+ tokens = client.extract_tokens(result)
147
+ bm25_tokens = client.extract_bm25_tokens(result)
148
+
149
+ print(f"Эмбеддинги: {embeddings}")
150
+ print(f"Тексты: {texts}")
151
+ print(f"Токены: {tokens}")
152
+ print(f"BM25 токены: {bm25_tokens}")
153
+
154
+ if __name__ == "__main__":
155
+ asyncio.run(main())
156
+ ```
157
+
158
+ ## Установка
159
+
160
+ ```bash
161
+ # Установка из PyPI
162
+ pip install embed-client
163
+
164
+ # Установка в режиме разработки
165
+ git clone <repository>
166
+ cd embed-client
167
+ pip install -e .
168
+ ```
169
+
170
+ ## Зависимости
171
+
172
+ - `aiohttp` - асинхронные HTTP запросы
173
+ - `PyJWT>=2.0.0` - JWT токены
174
+ - `cryptography>=3.0.0` - криптография и сертификаты
175
+ - `pydantic>=2.0.0` - валидация конфигурации
176
+
177
+ ## Тестирование
178
+
179
+ ```bash
180
+ # Запуск всех тестов
181
+ pytest tests/
182
+
183
+ # Запуск тестов с покрытием
184
+ pytest tests/ --cov=embed_client
185
+
186
+ # Запуск конкретных тестов
187
+ pytest tests/test_async_client.py -v
188
+ pytest tests/test_config.py -v
189
+ pytest tests/test_auth.py -v
190
+ pytest tests/test_ssl_manager.py -v
191
+ ```
192
+
193
+ ## Документация
194
+
195
+ - [Формат API и режимы безопасности](docs/api_format.md)
196
+ - [Примеры использования](embed_client/example_async_usage.py)
197
+ - [Примеры на русском](embed_client/example_async_usage_ru.py)
198
+
199
+ ## Безопасность
200
+
201
+ ### Рекомендации
202
+
203
+ 1. **Используйте HTTPS** для продакшена
204
+ 2. **Включите проверку сертификатов** (CERT_REQUIRED)
205
+ 3. **Используйте mTLS** для критически важных систем
206
+ 4. **Регулярно обновляйте сертификаты**
207
+ 5. **Храните приватные ключи в безопасном месте**
208
+
209
+ ### Поддерживаемые протоколы
210
+
211
+ - TLS 1.2
212
+ - TLS 1.3
213
+ - SSL 3.0 (устаревший, не рекомендуется)
214
+
215
+ ## Лицензия
216
+
217
+ MIT License
218
+
219
+ ## Автор
220
+
221
+ **Vasiliy Zdanovskiy**
222
+ Email: vasilyvz@gmail.com
223
+
224
+ ---
225
+
226
+ **Важно:**
227
+ - Используйте `--base-url` (через дефис), а не `--base_url` (через подчеркивание).
228
+ - Значение base_url должно содержать `http://` или `https://`.
229
+ - Аргументы должны быть отдельными (через пробел), а не через `=`.