embed-client 3.1.0.2__py3-none-any.whl → 3.1.0.3__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.
- embed_client/__main__.py +14 -0
- embed_client/cli.py +277 -0
- embed_client/demo_cli.py +82 -0
- embed_client/demo_security_cli.py +202 -0
- embed_client/security_cli.py +409 -0
- {embed_client-3.1.0.2.dist-info → embed_client-3.1.0.3.dist-info}/METADATA +2 -1
- {embed_client-3.1.0.2.dist-info → embed_client-3.1.0.3.dist-info}/RECORD +10 -5
- {embed_client-3.1.0.2.dist-info → embed_client-3.1.0.3.dist-info}/WHEEL +0 -0
- {embed_client-3.1.0.2.dist-info → embed_client-3.1.0.3.dist-info}/licenses/LICENSE +0 -0
- {embed_client-3.1.0.2.dist-info → embed_client-3.1.0.3.dist-info}/top_level.txt +0 -0
embed_client/__main__.py
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Main entry point for embed-client CLI.
|
4
|
+
|
5
|
+
Author: Vasiliy Zdanovskiy
|
6
|
+
email: vasilyvz@gmail.com
|
7
|
+
"""
|
8
|
+
|
9
|
+
import asyncio
|
10
|
+
import sys
|
11
|
+
from .cli import main
|
12
|
+
|
13
|
+
if __name__ == "__main__":
|
14
|
+
sys.exit(asyncio.run(main()))
|
embed_client/cli.py
ADDED
@@ -0,0 +1,277 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
CLI Application for Text Vectorization
|
4
|
+
Command-line interface for embedding text using embed-client.
|
5
|
+
|
6
|
+
Author: Vasiliy Zdanovskiy
|
7
|
+
email: vasilyvz@gmail.com
|
8
|
+
"""
|
9
|
+
|
10
|
+
import argparse
|
11
|
+
import asyncio
|
12
|
+
import json
|
13
|
+
import sys
|
14
|
+
from pathlib import Path
|
15
|
+
from typing import List, Optional, Dict, Any
|
16
|
+
|
17
|
+
from embed_client.async_client import EmbeddingServiceAsyncClient
|
18
|
+
from embed_client.client_factory import ClientFactory
|
19
|
+
|
20
|
+
|
21
|
+
class VectorizationCLI:
|
22
|
+
"""CLI application for text vectorization."""
|
23
|
+
|
24
|
+
def __init__(self):
|
25
|
+
self.client = None
|
26
|
+
|
27
|
+
async def connect(self, config: Dict[str, Any]):
|
28
|
+
"""Connect to embedding service."""
|
29
|
+
try:
|
30
|
+
self.client = EmbeddingServiceAsyncClient(config_dict=config)
|
31
|
+
await self.client.__aenter__()
|
32
|
+
return True
|
33
|
+
except Exception as e:
|
34
|
+
print(f"❌ Failed to connect: {e}")
|
35
|
+
return False
|
36
|
+
|
37
|
+
async def disconnect(self):
|
38
|
+
"""Disconnect from embedding service."""
|
39
|
+
if self.client:
|
40
|
+
await self.client.close()
|
41
|
+
|
42
|
+
async def health_check(self) -> bool:
|
43
|
+
"""Check service health."""
|
44
|
+
try:
|
45
|
+
result = await self.client.health()
|
46
|
+
print(f"✅ Service health: {result.get('status', 'unknown')}")
|
47
|
+
return True
|
48
|
+
except Exception as e:
|
49
|
+
print(f"❌ Health check failed: {e}")
|
50
|
+
return False
|
51
|
+
|
52
|
+
async def vectorize_texts(self, texts: List[str], output_format: str = "json") -> Optional[List[List[float]]]:
|
53
|
+
"""Vectorize texts."""
|
54
|
+
try:
|
55
|
+
params = {"texts": texts}
|
56
|
+
result = await self.client.cmd("embed", params=params)
|
57
|
+
|
58
|
+
# Extract embeddings from result
|
59
|
+
embeddings = self._extract_embeddings(result)
|
60
|
+
|
61
|
+
if output_format == "json":
|
62
|
+
print(json.dumps(embeddings, indent=2))
|
63
|
+
elif output_format == "csv":
|
64
|
+
self._print_csv(embeddings)
|
65
|
+
elif output_format == "vectors":
|
66
|
+
self._print_vectors(embeddings)
|
67
|
+
|
68
|
+
return embeddings
|
69
|
+
|
70
|
+
except Exception as e:
|
71
|
+
print(f"❌ Vectorization failed: {e}")
|
72
|
+
return None
|
73
|
+
|
74
|
+
def _extract_embeddings(self, result: Dict[str, Any]) -> List[List[float]]:
|
75
|
+
"""Extract embeddings from API response."""
|
76
|
+
# Handle different response formats
|
77
|
+
if "embeddings" in result:
|
78
|
+
return result["embeddings"]
|
79
|
+
|
80
|
+
if "result" in result:
|
81
|
+
res = result["result"]
|
82
|
+
|
83
|
+
if isinstance(res, list):
|
84
|
+
return res
|
85
|
+
|
86
|
+
if isinstance(res, dict):
|
87
|
+
if "embeddings" in res:
|
88
|
+
return res["embeddings"]
|
89
|
+
|
90
|
+
if "data" in res and isinstance(res["data"], list):
|
91
|
+
embeddings = []
|
92
|
+
for item in res["data"]:
|
93
|
+
if isinstance(item, dict) and "embedding" in item:
|
94
|
+
embeddings.append(item["embedding"])
|
95
|
+
else:
|
96
|
+
embeddings.append(item)
|
97
|
+
return embeddings
|
98
|
+
|
99
|
+
raise ValueError(f"Cannot extract embeddings from response: {result}")
|
100
|
+
|
101
|
+
def _print_csv(self, embeddings: List[List[float]]):
|
102
|
+
"""Print embeddings in CSV format."""
|
103
|
+
for i, embedding in enumerate(embeddings):
|
104
|
+
print(f"text_{i}," + ",".join(map(str, embedding)))
|
105
|
+
|
106
|
+
def _print_vectors(self, embeddings: List[List[float]]):
|
107
|
+
"""Print embeddings as vectors."""
|
108
|
+
for i, embedding in enumerate(embeddings):
|
109
|
+
print(f"Text {i}: [{', '.join(map(str, embedding))}]")
|
110
|
+
|
111
|
+
async def get_help(self, command: Optional[str] = None):
|
112
|
+
"""Get help information."""
|
113
|
+
try:
|
114
|
+
if command:
|
115
|
+
result = await self.client.cmd("help", params={"command": command})
|
116
|
+
else:
|
117
|
+
result = await self.client.cmd("help")
|
118
|
+
|
119
|
+
print(json.dumps(result, indent=2))
|
120
|
+
|
121
|
+
except Exception as e:
|
122
|
+
print(f"❌ Help request failed: {e}")
|
123
|
+
|
124
|
+
async def get_commands(self):
|
125
|
+
"""Get available commands."""
|
126
|
+
try:
|
127
|
+
result = await self.client.get_commands()
|
128
|
+
print(json.dumps(result, indent=2))
|
129
|
+
|
130
|
+
except Exception as e:
|
131
|
+
print(f"❌ Commands request failed: {e}")
|
132
|
+
|
133
|
+
|
134
|
+
def create_config_from_args(args) -> Dict[str, Any]:
|
135
|
+
"""Create configuration from command line arguments."""
|
136
|
+
config = {
|
137
|
+
"server": {
|
138
|
+
"host": args.host,
|
139
|
+
"port": args.port
|
140
|
+
},
|
141
|
+
"auth": {"method": "none"},
|
142
|
+
"ssl": {"enabled": False},
|
143
|
+
"security": {"enabled": False}
|
144
|
+
}
|
145
|
+
|
146
|
+
# Add authentication if specified
|
147
|
+
if args.api_key:
|
148
|
+
config["auth"]["method"] = "api_key"
|
149
|
+
config["security"] = {
|
150
|
+
"enabled": True,
|
151
|
+
"tokens": {"user": args.api_key}
|
152
|
+
}
|
153
|
+
|
154
|
+
# Add SSL if specified
|
155
|
+
if args.ssl:
|
156
|
+
config["ssl"]["enabled"] = True
|
157
|
+
if args.cert_file:
|
158
|
+
config["ssl"]["cert_file"] = args.cert_file
|
159
|
+
if args.key_file:
|
160
|
+
config["ssl"]["key_file"] = args.key_file
|
161
|
+
if args.ca_cert_file:
|
162
|
+
config["ssl"]["ca_cert_file"] = args.ca_cert_file
|
163
|
+
|
164
|
+
return config
|
165
|
+
|
166
|
+
|
167
|
+
async def main():
|
168
|
+
"""Main CLI function."""
|
169
|
+
parser = argparse.ArgumentParser(
|
170
|
+
description="CLI Application for Text Vectorization",
|
171
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
172
|
+
epilog="""
|
173
|
+
Examples:
|
174
|
+
# Vectorize text from command line
|
175
|
+
python -m embed_client.cli vectorize "hello world" "test text"
|
176
|
+
|
177
|
+
# Vectorize text from file
|
178
|
+
python -m embed_client.cli vectorize --file texts.txt
|
179
|
+
|
180
|
+
# Use HTTPS with authentication
|
181
|
+
python -m embed_client.cli vectorize "hello world" --host https://localhost --port 8443 --api-key your-key
|
182
|
+
|
183
|
+
# Use mTLS
|
184
|
+
python -m embed_client.cli vectorize "hello world" --ssl --cert-file client.crt --key-file client.key
|
185
|
+
|
186
|
+
# Get help from service
|
187
|
+
python -m embed_client.cli help
|
188
|
+
|
189
|
+
# Check service health
|
190
|
+
python -m embed_client.cli health
|
191
|
+
"""
|
192
|
+
)
|
193
|
+
|
194
|
+
# Connection options
|
195
|
+
parser.add_argument("--host", default="http://localhost", help="Server host (default: http://localhost)")
|
196
|
+
parser.add_argument("--port", type=int, default=8001, help="Server port (default: 8001)")
|
197
|
+
parser.add_argument("--api-key", help="API key for authentication")
|
198
|
+
parser.add_argument("--ssl", action="store_true", help="Enable SSL/TLS")
|
199
|
+
parser.add_argument("--cert-file", help="Client certificate file")
|
200
|
+
parser.add_argument("--key-file", help="Client private key file")
|
201
|
+
parser.add_argument("--ca-cert-file", help="CA certificate file")
|
202
|
+
|
203
|
+
# Output options
|
204
|
+
parser.add_argument("--format", choices=["json", "csv", "vectors"], default="json", help="Output format")
|
205
|
+
parser.add_argument("--output", "-o", help="Output file (default: stdout)")
|
206
|
+
|
207
|
+
# Commands
|
208
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
209
|
+
|
210
|
+
# Vectorize command
|
211
|
+
vectorize_parser = subparsers.add_parser("vectorize", help="Vectorize text")
|
212
|
+
vectorize_parser.add_argument("texts", nargs="*", help="Texts to vectorize")
|
213
|
+
vectorize_parser.add_argument("--file", "-f", help="File containing texts (one per line)")
|
214
|
+
|
215
|
+
# Health command
|
216
|
+
health_parser = subparsers.add_parser("health", help="Check service health")
|
217
|
+
|
218
|
+
# Help command
|
219
|
+
help_parser = subparsers.add_parser("help", help="Get help from service")
|
220
|
+
help_parser.add_argument("--command", help="Specific command to get help for")
|
221
|
+
|
222
|
+
# Commands command
|
223
|
+
commands_parser = subparsers.add_parser("commands", help="Get available commands")
|
224
|
+
|
225
|
+
args = parser.parse_args()
|
226
|
+
|
227
|
+
if not args.command:
|
228
|
+
parser.print_help()
|
229
|
+
return 1
|
230
|
+
|
231
|
+
# Create configuration
|
232
|
+
config = create_config_from_args(args)
|
233
|
+
|
234
|
+
# Create CLI instance
|
235
|
+
cli = VectorizationCLI()
|
236
|
+
|
237
|
+
try:
|
238
|
+
# Connect to service
|
239
|
+
if not await cli.connect(config):
|
240
|
+
return 1
|
241
|
+
|
242
|
+
# Execute command
|
243
|
+
if args.command == "vectorize":
|
244
|
+
texts = args.texts
|
245
|
+
if args.file:
|
246
|
+
with open(args.file, 'r') as f:
|
247
|
+
texts.extend(line.strip() for line in f if line.strip())
|
248
|
+
|
249
|
+
if not texts:
|
250
|
+
print("❌ No texts provided")
|
251
|
+
return 1
|
252
|
+
|
253
|
+
await cli.vectorize_texts(texts, args.format)
|
254
|
+
|
255
|
+
elif args.command == "health":
|
256
|
+
await cli.health_check()
|
257
|
+
|
258
|
+
elif args.command == "help":
|
259
|
+
await cli.get_help(args.command)
|
260
|
+
|
261
|
+
elif args.command == "commands":
|
262
|
+
await cli.get_commands()
|
263
|
+
|
264
|
+
return 0
|
265
|
+
|
266
|
+
except KeyboardInterrupt:
|
267
|
+
print("\n🛑 Interrupted by user")
|
268
|
+
return 1
|
269
|
+
except Exception as e:
|
270
|
+
print(f"❌ Error: {e}")
|
271
|
+
return 1
|
272
|
+
finally:
|
273
|
+
await cli.disconnect()
|
274
|
+
|
275
|
+
|
276
|
+
if __name__ == "__main__":
|
277
|
+
sys.exit(asyncio.run(main()))
|
embed_client/demo_cli.py
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Demo CLI Application for Text Vectorization
|
4
|
+
Simple demonstration of the CLI functionality.
|
5
|
+
|
6
|
+
Author: Vasiliy Zdanovskiy
|
7
|
+
email: vasilyvz@gmail.com
|
8
|
+
"""
|
9
|
+
|
10
|
+
import asyncio
|
11
|
+
import sys
|
12
|
+
from pathlib import Path
|
13
|
+
|
14
|
+
# Add the project root to the path
|
15
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
16
|
+
|
17
|
+
from embed_client.cli import VectorizationCLI
|
18
|
+
|
19
|
+
|
20
|
+
async def demo_vectorization():
|
21
|
+
"""Demonstrate vectorization functionality."""
|
22
|
+
print("🚀 Embed-Client CLI Demo")
|
23
|
+
print("=" * 40)
|
24
|
+
|
25
|
+
# Configuration for demo
|
26
|
+
config = {
|
27
|
+
"server": {"host": "http://localhost", "port": 8001},
|
28
|
+
"auth": {"method": "none"},
|
29
|
+
"ssl": {"enabled": False},
|
30
|
+
"security": {"enabled": False}
|
31
|
+
}
|
32
|
+
|
33
|
+
cli = VectorizationCLI()
|
34
|
+
|
35
|
+
try:
|
36
|
+
# Connect to service
|
37
|
+
print("🔌 Connecting to embedding service...")
|
38
|
+
if not await cli.connect(config):
|
39
|
+
print("❌ Failed to connect to service")
|
40
|
+
return
|
41
|
+
|
42
|
+
# Health check
|
43
|
+
print("\n🏥 Checking service health...")
|
44
|
+
await cli.health_check()
|
45
|
+
|
46
|
+
# Get help
|
47
|
+
print("\n❓ Getting help from service...")
|
48
|
+
await cli.get_help()
|
49
|
+
|
50
|
+
# Get commands
|
51
|
+
print("\n📋 Getting available commands...")
|
52
|
+
await cli.get_commands()
|
53
|
+
|
54
|
+
# Vectorize texts
|
55
|
+
print("\n🔤 Vectorizing texts...")
|
56
|
+
texts = [
|
57
|
+
"Hello world",
|
58
|
+
"This is a test",
|
59
|
+
"Machine learning is awesome"
|
60
|
+
]
|
61
|
+
|
62
|
+
print(f"📝 Vectorizing {len(texts)} texts:")
|
63
|
+
for i, text in enumerate(texts):
|
64
|
+
print(f" {i+1}. {text}")
|
65
|
+
|
66
|
+
await cli.vectorize_texts(texts, "json")
|
67
|
+
|
68
|
+
print("\n✅ Demo completed successfully!")
|
69
|
+
|
70
|
+
except Exception as e:
|
71
|
+
print(f"❌ Demo failed: {e}")
|
72
|
+
finally:
|
73
|
+
await cli.disconnect()
|
74
|
+
|
75
|
+
|
76
|
+
async def main():
|
77
|
+
"""Main demo function."""
|
78
|
+
await demo_vectorization()
|
79
|
+
|
80
|
+
|
81
|
+
if __name__ == "__main__":
|
82
|
+
asyncio.run(main())
|
@@ -0,0 +1,202 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Demo Security CLI Application
|
4
|
+
Demonstrates all 8 security modes using the security CLI.
|
5
|
+
|
6
|
+
Author: Vasiliy Zdanovskiy
|
7
|
+
email: vasilyvz@gmail.com
|
8
|
+
"""
|
9
|
+
|
10
|
+
import asyncio
|
11
|
+
import sys
|
12
|
+
from pathlib import Path
|
13
|
+
|
14
|
+
# Add the project root to the path
|
15
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
16
|
+
|
17
|
+
from embed_client.security_cli import SecurityCLI, create_config_from_security_mode
|
18
|
+
|
19
|
+
|
20
|
+
async def demo_security_mode(mode: str, host: str, port: int, **kwargs):
|
21
|
+
"""Demonstrate a specific security mode."""
|
22
|
+
print(f"\n🔐 Demonstrating {mode} mode...")
|
23
|
+
print("=" * 50)
|
24
|
+
|
25
|
+
# Create configuration
|
26
|
+
config = create_config_from_security_mode(mode, host, port, **kwargs)
|
27
|
+
|
28
|
+
cli = SecurityCLI()
|
29
|
+
|
30
|
+
try:
|
31
|
+
# Connect to service
|
32
|
+
print(f"🔌 Connecting using {mode} mode...")
|
33
|
+
if not await cli.connect(config):
|
34
|
+
print(f"❌ Failed to connect using {mode} mode")
|
35
|
+
return False
|
36
|
+
|
37
|
+
# Health check
|
38
|
+
print(f"🏥 Checking service health...")
|
39
|
+
health_ok = await cli.health_check()
|
40
|
+
if not health_ok:
|
41
|
+
print(f"❌ Health check failed for {mode} mode")
|
42
|
+
return False
|
43
|
+
|
44
|
+
# Get help
|
45
|
+
print(f"❓ Getting help from service...")
|
46
|
+
await cli.get_help()
|
47
|
+
|
48
|
+
# Get commands
|
49
|
+
print(f"📋 Getting available commands...")
|
50
|
+
await cli.get_commands()
|
51
|
+
|
52
|
+
# Vectorize texts
|
53
|
+
texts = [
|
54
|
+
f"Hello from {mode} mode",
|
55
|
+
f"Security test for {mode}",
|
56
|
+
f"Vectorization using {mode}"
|
57
|
+
]
|
58
|
+
|
59
|
+
print(f"🔤 Vectorizing {len(texts)} texts using {mode} mode...")
|
60
|
+
for i, text in enumerate(texts):
|
61
|
+
print(f" {i+1}. {text}")
|
62
|
+
|
63
|
+
# Test different output formats
|
64
|
+
print(f"\n📊 Testing JSON format:")
|
65
|
+
await cli.vectorize_texts(texts, "json")
|
66
|
+
|
67
|
+
print(f"\n📊 Testing CSV format:")
|
68
|
+
await cli.vectorize_texts(texts, "csv")
|
69
|
+
|
70
|
+
print(f"\n📊 Testing vectors format:")
|
71
|
+
await cli.vectorize_texts(texts, "vectors")
|
72
|
+
|
73
|
+
print(f"✅ {mode} mode demonstration completed successfully!")
|
74
|
+
return True
|
75
|
+
|
76
|
+
except Exception as e:
|
77
|
+
print(f"❌ {mode} mode demonstration failed: {e}")
|
78
|
+
return False
|
79
|
+
finally:
|
80
|
+
await cli.disconnect()
|
81
|
+
|
82
|
+
|
83
|
+
async def demo_all_security_modes():
|
84
|
+
"""Demonstrate all 8 security modes."""
|
85
|
+
print("🚀 Security CLI Demo - All 8 Security Modes")
|
86
|
+
print("=" * 60)
|
87
|
+
|
88
|
+
# Demo configurations for each mode
|
89
|
+
demos = [
|
90
|
+
{
|
91
|
+
"mode": "http",
|
92
|
+
"host": "localhost",
|
93
|
+
"port": 10001,
|
94
|
+
"description": "Plain HTTP without authentication"
|
95
|
+
},
|
96
|
+
{
|
97
|
+
"mode": "http_token",
|
98
|
+
"host": "localhost",
|
99
|
+
"port": 10002,
|
100
|
+
"api_key": "admin-secret-key",
|
101
|
+
"description": "HTTP with API Key authentication"
|
102
|
+
},
|
103
|
+
{
|
104
|
+
"mode": "http_token_roles",
|
105
|
+
"host": "localhost",
|
106
|
+
"port": 10003,
|
107
|
+
"api_key": "admin-secret-key",
|
108
|
+
"description": "HTTP with API Key and role-based access control"
|
109
|
+
},
|
110
|
+
{
|
111
|
+
"mode": "https",
|
112
|
+
"host": "localhost",
|
113
|
+
"port": 10011,
|
114
|
+
"cert_file": "/home/vasilyvz/projects/embed-client/mtls_certificates/server/embedding-service.crt",
|
115
|
+
"key_file": "/home/vasilyvz/projects/embed-client/mtls_certificates/server/embedding-service.key",
|
116
|
+
"description": "HTTPS with server certificate verification"
|
117
|
+
},
|
118
|
+
{
|
119
|
+
"mode": "https_token",
|
120
|
+
"host": "localhost",
|
121
|
+
"port": 10012,
|
122
|
+
"api_key": "admin-secret-key",
|
123
|
+
"cert_file": "/home/vasilyvz/projects/embed-client/mtls_certificates/server/embedding-service.crt",
|
124
|
+
"key_file": "/home/vasilyvz/projects/embed-client/mtls_certificates/server/embedding-service.key",
|
125
|
+
"description": "HTTPS with server certificates + authentication"
|
126
|
+
},
|
127
|
+
{
|
128
|
+
"mode": "https_token_roles",
|
129
|
+
"host": "localhost",
|
130
|
+
"port": 10013,
|
131
|
+
"api_key": "admin-secret-key",
|
132
|
+
"cert_file": "/home/vasilyvz/projects/embed-client/mtls_certificates/server/embedding-service.crt",
|
133
|
+
"key_file": "/home/vasilyvz/projects/embed-client/mtls_certificates/server/embedding-service.key",
|
134
|
+
"description": "HTTPS with server certificates + authentication + roles"
|
135
|
+
},
|
136
|
+
{
|
137
|
+
"mode": "mtls",
|
138
|
+
"host": "localhost",
|
139
|
+
"port": 10021,
|
140
|
+
"cert_file": "/home/vasilyvz/projects/embed-client/mtls_certificates/client/embedding-service.crt",
|
141
|
+
"key_file": "/home/vasilyvz/projects/embed-client/mtls_certificates/client/embedding-service.key",
|
142
|
+
"ca_cert_file": "/home/vasilyvz/projects/embed-client/mtls_certificates/ca/ca.crt",
|
143
|
+
"description": "Mutual TLS with client and server certificates"
|
144
|
+
},
|
145
|
+
{
|
146
|
+
"mode": "mtls_roles",
|
147
|
+
"host": "localhost",
|
148
|
+
"port": 10022,
|
149
|
+
"cert_file": "/home/vasilyvz/projects/embed-client/mtls_certificates/client/embedding-service.crt",
|
150
|
+
"key_file": "/home/vasilyvz/projects/embed-client/mtls_certificates/client/embedding-service.key",
|
151
|
+
"ca_cert_file": "/home/vasilyvz/projects/embed-client/mtls_certificates/ca/ca.crt",
|
152
|
+
"description": "mTLS with role-based access control"
|
153
|
+
}
|
154
|
+
]
|
155
|
+
|
156
|
+
results = {}
|
157
|
+
|
158
|
+
for demo in demos:
|
159
|
+
mode = demo["mode"]
|
160
|
+
description = demo["description"]
|
161
|
+
|
162
|
+
print(f"\n🔐 {mode.upper()} - {description}")
|
163
|
+
print("-" * 50)
|
164
|
+
|
165
|
+
# Extract parameters for the demo
|
166
|
+
demo_params = {k: v for k, v in demo.items() if k not in ["mode", "host", "port", "description"]}
|
167
|
+
|
168
|
+
success = await demo_security_mode(
|
169
|
+
demo["mode"],
|
170
|
+
demo["host"],
|
171
|
+
demo["port"],
|
172
|
+
**demo_params
|
173
|
+
)
|
174
|
+
|
175
|
+
results[mode] = success
|
176
|
+
|
177
|
+
# Print summary
|
178
|
+
print("\n📊 Demo Results Summary:")
|
179
|
+
print("=" * 50)
|
180
|
+
for mode, success in results.items():
|
181
|
+
status = "✅ SUCCESS" if success else "❌ FAILED"
|
182
|
+
print(f"{mode:20} {status}")
|
183
|
+
|
184
|
+
total_demos = len(results)
|
185
|
+
successful_demos = sum(1 for success in results.values() if success)
|
186
|
+
failed_demos = total_demos - successful_demos
|
187
|
+
|
188
|
+
print(f"\n🎉 Demo completed!")
|
189
|
+
print(f"📊 Results: {successful_demos}/{total_demos} demos successful")
|
190
|
+
if failed_demos > 0:
|
191
|
+
print(f"❌ {failed_demos} demos failed")
|
192
|
+
else:
|
193
|
+
print("✅ All demos successful!")
|
194
|
+
|
195
|
+
|
196
|
+
async def main():
|
197
|
+
"""Main demo function."""
|
198
|
+
await demo_all_security_modes()
|
199
|
+
|
200
|
+
|
201
|
+
if __name__ == "__main__":
|
202
|
+
asyncio.run(main())
|
@@ -0,0 +1,409 @@
|
|
1
|
+
#!/usr/bin/env python3
|
2
|
+
"""
|
3
|
+
Security CLI Application for Text Vectorization
|
4
|
+
Command-line interface supporting all 8 security modes.
|
5
|
+
|
6
|
+
Author: Vasiliy Zdanovskiy
|
7
|
+
email: vasilyvz@gmail.com
|
8
|
+
"""
|
9
|
+
|
10
|
+
import argparse
|
11
|
+
import asyncio
|
12
|
+
import json
|
13
|
+
import sys
|
14
|
+
from pathlib import Path
|
15
|
+
from typing import List, Optional, Dict, Any
|
16
|
+
|
17
|
+
from embed_client.async_client import EmbeddingServiceAsyncClient
|
18
|
+
from embed_client.client_factory import ClientFactory
|
19
|
+
|
20
|
+
|
21
|
+
class SecurityCLI:
|
22
|
+
"""CLI application supporting all 8 security modes."""
|
23
|
+
|
24
|
+
def __init__(self):
|
25
|
+
self.client = None
|
26
|
+
|
27
|
+
async def connect(self, config: Dict[str, Any]):
|
28
|
+
"""Connect to embedding service."""
|
29
|
+
try:
|
30
|
+
# Создаем клиент, но не входим в контекст пока
|
31
|
+
self.client = EmbeddingServiceAsyncClient(config_dict=config)
|
32
|
+
return True
|
33
|
+
except Exception as e:
|
34
|
+
print(f"❌ Failed to connect: {e}")
|
35
|
+
return False
|
36
|
+
|
37
|
+
async def disconnect(self):
|
38
|
+
"""Disconnect from embedding service."""
|
39
|
+
if self.client:
|
40
|
+
await self.client.close()
|
41
|
+
|
42
|
+
async def health_check(self) -> bool:
|
43
|
+
"""Check service health."""
|
44
|
+
try:
|
45
|
+
async with self.client as client:
|
46
|
+
result = await client.health()
|
47
|
+
print(f"✅ Service health: {result.get('status', 'unknown')}")
|
48
|
+
return True
|
49
|
+
except Exception as e:
|
50
|
+
print(f"❌ Health check failed: {e}")
|
51
|
+
return False
|
52
|
+
|
53
|
+
async def vectorize_texts(self, texts: List[str], output_format: str = "json") -> Optional[List[List[float]]]:
|
54
|
+
"""Vectorize texts."""
|
55
|
+
try:
|
56
|
+
params = {"texts": texts}
|
57
|
+
result = await self.client.cmd("embed", params=params)
|
58
|
+
|
59
|
+
# Extract embeddings from result
|
60
|
+
embeddings = self._extract_embeddings(result)
|
61
|
+
|
62
|
+
if output_format == "json":
|
63
|
+
print(json.dumps(embeddings, indent=2))
|
64
|
+
elif output_format == "csv":
|
65
|
+
self._print_csv(embeddings)
|
66
|
+
elif output_format == "vectors":
|
67
|
+
self._print_vectors(embeddings)
|
68
|
+
|
69
|
+
return embeddings
|
70
|
+
|
71
|
+
except Exception as e:
|
72
|
+
print(f"❌ Vectorization failed: {e}")
|
73
|
+
return None
|
74
|
+
|
75
|
+
def _extract_embeddings(self, result: Dict[str, Any]) -> List[List[float]]:
|
76
|
+
"""Extract embeddings from API response."""
|
77
|
+
# Handle different response formats
|
78
|
+
if "embeddings" in result:
|
79
|
+
return result["embeddings"]
|
80
|
+
|
81
|
+
if "result" in result:
|
82
|
+
res = result["result"]
|
83
|
+
|
84
|
+
if isinstance(res, list):
|
85
|
+
return res
|
86
|
+
|
87
|
+
if isinstance(res, dict):
|
88
|
+
if "embeddings" in res:
|
89
|
+
return res["embeddings"]
|
90
|
+
|
91
|
+
if "data" in res and isinstance(res["data"], list):
|
92
|
+
embeddings = []
|
93
|
+
for item in res["data"]:
|
94
|
+
if isinstance(item, dict) and "embedding" in item:
|
95
|
+
embeddings.append(item["embedding"])
|
96
|
+
else:
|
97
|
+
embeddings.append(item)
|
98
|
+
return embeddings
|
99
|
+
|
100
|
+
raise ValueError(f"Cannot extract embeddings from response: {result}")
|
101
|
+
|
102
|
+
def _print_csv(self, embeddings: List[List[float]]):
|
103
|
+
"""Print embeddings in CSV format."""
|
104
|
+
for i, embedding in enumerate(embeddings):
|
105
|
+
print(f"text_{i}," + ",".join(map(str, embedding)))
|
106
|
+
|
107
|
+
def _print_vectors(self, embeddings: List[List[float]]):
|
108
|
+
"""Print embeddings as vectors."""
|
109
|
+
for i, embedding in enumerate(embeddings):
|
110
|
+
print(f"Text {i}: [{', '.join(map(str, embedding))}]")
|
111
|
+
|
112
|
+
async def get_help(self, command: Optional[str] = None):
|
113
|
+
"""Get help information."""
|
114
|
+
try:
|
115
|
+
if command:
|
116
|
+
result = await self.client.cmd("help", params={"command": command})
|
117
|
+
else:
|
118
|
+
result = await self.client.cmd("help")
|
119
|
+
|
120
|
+
print(json.dumps(result, indent=2))
|
121
|
+
|
122
|
+
except Exception as e:
|
123
|
+
print(f"❌ Help request failed: {e}")
|
124
|
+
|
125
|
+
async def get_commands(self):
|
126
|
+
"""Get available commands."""
|
127
|
+
try:
|
128
|
+
result = await self.client.get_commands()
|
129
|
+
print(json.dumps(result, indent=2))
|
130
|
+
|
131
|
+
except Exception as e:
|
132
|
+
print(f"❌ Commands request failed: {e}")
|
133
|
+
|
134
|
+
|
135
|
+
def create_config_from_security_mode(security_mode: str, host: str, port: int, **kwargs) -> Dict[str, Any]:
|
136
|
+
"""Create configuration based on security mode."""
|
137
|
+
|
138
|
+
# Base configuration
|
139
|
+
config = {
|
140
|
+
"server": {"host": host, "port": port},
|
141
|
+
"auth": {"method": "none"},
|
142
|
+
"ssl": {"enabled": False},
|
143
|
+
"security": {"enabled": False}
|
144
|
+
}
|
145
|
+
|
146
|
+
if security_mode == "http":
|
147
|
+
# HTTP - plain HTTP without authentication
|
148
|
+
config["server"]["host"] = f"http://{host.split('://')[-1]}"
|
149
|
+
|
150
|
+
elif security_mode == "http_token":
|
151
|
+
# HTTP + Token - HTTP with API Key authentication
|
152
|
+
config["server"]["host"] = f"http://{host.split('://')[-1]}"
|
153
|
+
config["auth"]["method"] = "api_key"
|
154
|
+
config["security"] = {
|
155
|
+
"enabled": True,
|
156
|
+
"tokens": {"user": kwargs.get("api_key", "admin-secret-key")}
|
157
|
+
}
|
158
|
+
|
159
|
+
elif security_mode == "http_token_roles":
|
160
|
+
# HTTP + Token + Roles - HTTP with API Key and role-based access control
|
161
|
+
config["server"]["host"] = f"http://{host.split('://')[-1]}"
|
162
|
+
config["auth"]["method"] = "api_key"
|
163
|
+
config["security"] = {
|
164
|
+
"enabled": True,
|
165
|
+
"tokens": {"user": kwargs.get("api_key", "admin-secret-key")},
|
166
|
+
"roles": {
|
167
|
+
"admin": ["read", "write", "delete", "admin"],
|
168
|
+
"user": ["read", "write"],
|
169
|
+
"readonly": ["read"]
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
elif security_mode == "https":
|
174
|
+
# HTTPS - HTTPS with server certificate verification
|
175
|
+
config["server"]["host"] = f"https://{host.split('://')[-1]}"
|
176
|
+
config["ssl"] = {
|
177
|
+
"enabled": True,
|
178
|
+
"verify": False, # Отключаем проверку для самоподписанных сертификатов
|
179
|
+
"cert_file": kwargs.get("cert_file"),
|
180
|
+
"key_file": kwargs.get("key_file"),
|
181
|
+
"ca_cert_file": kwargs.get("ca_cert_file")
|
182
|
+
}
|
183
|
+
|
184
|
+
elif security_mode == "https_token":
|
185
|
+
# HTTPS + Token - HTTPS with server certificates + authentication
|
186
|
+
config["server"]["host"] = f"https://{host.split('://')[-1]}"
|
187
|
+
config["auth"]["method"] = "api_key"
|
188
|
+
config["ssl"] = {
|
189
|
+
"enabled": True,
|
190
|
+
"cert_file": kwargs.get("cert_file"),
|
191
|
+
"key_file": kwargs.get("key_file")
|
192
|
+
}
|
193
|
+
config["security"] = {
|
194
|
+
"enabled": True,
|
195
|
+
"tokens": {"user": kwargs.get("api_key", "admin-secret-key")}
|
196
|
+
}
|
197
|
+
|
198
|
+
elif security_mode == "https_token_roles":
|
199
|
+
# HTTPS + Token + Roles - HTTPS with server certificates + authentication + roles
|
200
|
+
config["server"]["host"] = f"https://{host.split('://')[-1]}"
|
201
|
+
config["auth"]["method"] = "api_key"
|
202
|
+
config["ssl"] = {
|
203
|
+
"enabled": True,
|
204
|
+
"cert_file": kwargs.get("cert_file"),
|
205
|
+
"key_file": kwargs.get("key_file")
|
206
|
+
}
|
207
|
+
config["security"] = {
|
208
|
+
"enabled": True,
|
209
|
+
"tokens": {"user": kwargs.get("api_key", "admin-secret-key")},
|
210
|
+
"roles": {
|
211
|
+
"admin": ["read", "write", "delete", "admin"],
|
212
|
+
"user": ["read", "write"],
|
213
|
+
"readonly": ["read"]
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
elif security_mode == "mtls":
|
218
|
+
# mTLS - mutual TLS with client and server certificates
|
219
|
+
config["server"]["host"] = f"https://{host.split('://')[-1]}"
|
220
|
+
config["auth"]["method"] = "certificate"
|
221
|
+
config["ssl"] = {
|
222
|
+
"enabled": True,
|
223
|
+
"cert_file": kwargs.get("cert_file"),
|
224
|
+
"key_file": kwargs.get("key_file"),
|
225
|
+
"ca_cert_file": kwargs.get("ca_cert_file")
|
226
|
+
}
|
227
|
+
|
228
|
+
elif security_mode == "mtls_roles":
|
229
|
+
# mTLS + Roles - mTLS with role-based access control
|
230
|
+
config["server"]["host"] = f"https://{host.split('://')[-1]}"
|
231
|
+
config["auth"]["method"] = "certificate"
|
232
|
+
config["ssl"] = {
|
233
|
+
"enabled": True,
|
234
|
+
"cert_file": kwargs.get("cert_file"),
|
235
|
+
"key_file": kwargs.get("key_file"),
|
236
|
+
"ca_cert_file": kwargs.get("ca_cert_file")
|
237
|
+
}
|
238
|
+
config["security"] = {
|
239
|
+
"enabled": True,
|
240
|
+
"roles": {
|
241
|
+
"admin": ["read", "write", "delete", "admin"],
|
242
|
+
"user": ["read", "write"],
|
243
|
+
"readonly": ["read"]
|
244
|
+
}
|
245
|
+
}
|
246
|
+
|
247
|
+
return config
|
248
|
+
|
249
|
+
|
250
|
+
async def main():
|
251
|
+
"""Main CLI function."""
|
252
|
+
parser = argparse.ArgumentParser(
|
253
|
+
description="Security CLI Application for Text Vectorization",
|
254
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
255
|
+
epilog="""
|
256
|
+
Security Modes:
|
257
|
+
http - Plain HTTP without authentication
|
258
|
+
http_token - HTTP with API Key authentication
|
259
|
+
http_token_roles - HTTP with API Key and role-based access control
|
260
|
+
https - HTTPS with server certificate verification
|
261
|
+
https_token - HTTPS with server certificates + authentication
|
262
|
+
https_token_roles - HTTPS with server certificates + authentication + roles
|
263
|
+
mtls - Mutual TLS with client and server certificates
|
264
|
+
mtls_roles - mTLS with role-based access control
|
265
|
+
|
266
|
+
Examples:
|
267
|
+
# HTTP without authentication
|
268
|
+
python -m embed_client.security_cli vectorize --mode http "hello world"
|
269
|
+
|
270
|
+
# HTTP with token authentication
|
271
|
+
python -m embed_client.security_cli vectorize --mode http_token --api-key your-key "hello world"
|
272
|
+
|
273
|
+
# HTTPS with server certificates
|
274
|
+
python -m embed_client.security_cli vectorize --mode https --cert-file server.crt --key-file server.key "hello world"
|
275
|
+
|
276
|
+
# mTLS with client certificates
|
277
|
+
python -m embed_client.security_cli vectorize --mode mtls --cert-file client.crt --key-file client.key --ca-cert-file ca.crt "hello world"
|
278
|
+
|
279
|
+
# mTLS with roles
|
280
|
+
python -m embed_client.security_cli vectorize --mode mtls_roles --cert-file client.crt --key-file client.key --ca-cert-file ca.crt "hello world"
|
281
|
+
"""
|
282
|
+
)
|
283
|
+
|
284
|
+
# Security mode selection
|
285
|
+
parser.add_argument(
|
286
|
+
"--mode",
|
287
|
+
choices=[
|
288
|
+
"http", "http_token", "http_token_roles",
|
289
|
+
"https", "https_token", "https_token_roles",
|
290
|
+
"mtls", "mtls_roles"
|
291
|
+
],
|
292
|
+
required=True,
|
293
|
+
help="Security mode to use"
|
294
|
+
)
|
295
|
+
|
296
|
+
# Connection options
|
297
|
+
parser.add_argument("--host", default="localhost", help="Server host (default: localhost)")
|
298
|
+
parser.add_argument("--port", type=int, default=8001, help="Server port (default: 8001)")
|
299
|
+
|
300
|
+
# Authentication options
|
301
|
+
parser.add_argument("--api-key", help="API key for authentication (required for token modes)")
|
302
|
+
|
303
|
+
# SSL/TLS options
|
304
|
+
parser.add_argument("--cert-file", help="Certificate file (required for HTTPS/mTLS modes)")
|
305
|
+
parser.add_argument("--key-file", help="Private key file (required for HTTPS/mTLS modes)")
|
306
|
+
parser.add_argument("--ca-cert-file", help="CA certificate file (required for mTLS modes)")
|
307
|
+
|
308
|
+
# Output options
|
309
|
+
parser.add_argument("--format", choices=["json", "csv", "vectors"], default="json", help="Output format")
|
310
|
+
parser.add_argument("--output", "-o", help="Output file (default: stdout)")
|
311
|
+
|
312
|
+
# Commands
|
313
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
314
|
+
|
315
|
+
# Vectorize command
|
316
|
+
vectorize_parser = subparsers.add_parser("vectorize", help="Vectorize text")
|
317
|
+
vectorize_parser.add_argument("texts", nargs="*", help="Texts to vectorize")
|
318
|
+
vectorize_parser.add_argument("--file", "-f", help="File containing texts (one per line)")
|
319
|
+
|
320
|
+
# Health command
|
321
|
+
health_parser = subparsers.add_parser("health", help="Check service health")
|
322
|
+
|
323
|
+
# Help command
|
324
|
+
help_parser = subparsers.add_parser("help", help="Get help from service")
|
325
|
+
help_parser.add_argument("--command", help="Specific command to get help for")
|
326
|
+
|
327
|
+
# Commands command
|
328
|
+
commands_parser = subparsers.add_parser("commands", help="Get available commands")
|
329
|
+
|
330
|
+
args = parser.parse_args()
|
331
|
+
|
332
|
+
if not args.command:
|
333
|
+
parser.print_help()
|
334
|
+
return 1
|
335
|
+
|
336
|
+
# Validate required arguments based on security mode
|
337
|
+
if "token" in args.mode and not args.api_key:
|
338
|
+
print("❌ API key required for token-based authentication modes")
|
339
|
+
return 1
|
340
|
+
|
341
|
+
if "https" in args.mode or "mtls" in args.mode:
|
342
|
+
if not args.cert_file or not args.key_file:
|
343
|
+
print("❌ Certificate and key files required for HTTPS/mTLS modes")
|
344
|
+
return 1
|
345
|
+
|
346
|
+
if "mtls" in args.mode and not args.ca_cert_file:
|
347
|
+
print("❌ CA certificate file required for mTLS modes")
|
348
|
+
return 1
|
349
|
+
|
350
|
+
# Create configuration
|
351
|
+
config = create_config_from_security_mode(
|
352
|
+
args.mode,
|
353
|
+
args.host,
|
354
|
+
args.port,
|
355
|
+
api_key=args.api_key,
|
356
|
+
cert_file=args.cert_file,
|
357
|
+
key_file=args.key_file,
|
358
|
+
ca_cert_file=args.ca_cert_file
|
359
|
+
)
|
360
|
+
|
361
|
+
# Create CLI instance
|
362
|
+
cli = SecurityCLI()
|
363
|
+
|
364
|
+
try:
|
365
|
+
# Connect to service
|
366
|
+
print(f"🔌 Connecting using {args.mode} mode...")
|
367
|
+
if not await cli.connect(config):
|
368
|
+
return 1
|
369
|
+
|
370
|
+
# Execute command
|
371
|
+
if args.command == "vectorize":
|
372
|
+
texts = args.texts
|
373
|
+
if args.file:
|
374
|
+
with open(args.file, 'r') as f:
|
375
|
+
texts.extend(line.strip() for line in f if line.strip())
|
376
|
+
|
377
|
+
if not texts:
|
378
|
+
print("❌ No texts provided")
|
379
|
+
return 1
|
380
|
+
|
381
|
+
print(f"🔤 Vectorizing {len(texts)} texts using {args.mode} mode...")
|
382
|
+
await cli.vectorize_texts(texts, args.format)
|
383
|
+
|
384
|
+
elif args.command == "health":
|
385
|
+
print(f"🏥 Checking service health using {args.mode} mode...")
|
386
|
+
await cli.health_check()
|
387
|
+
|
388
|
+
elif args.command == "help":
|
389
|
+
print(f"❓ Getting help using {args.mode} mode...")
|
390
|
+
await cli.get_help(args.command)
|
391
|
+
|
392
|
+
elif args.command == "commands":
|
393
|
+
print(f"📋 Getting commands using {args.mode} mode...")
|
394
|
+
await cli.get_commands()
|
395
|
+
|
396
|
+
return 0
|
397
|
+
|
398
|
+
except KeyboardInterrupt:
|
399
|
+
print("\n🛑 Interrupted by user")
|
400
|
+
return 1
|
401
|
+
except Exception as e:
|
402
|
+
print(f"❌ Error: {e}")
|
403
|
+
return 1
|
404
|
+
finally:
|
405
|
+
await cli.disconnect()
|
406
|
+
|
407
|
+
|
408
|
+
if __name__ == "__main__":
|
409
|
+
sys.exit(asyncio.run(main()))
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: embed-client
|
3
|
-
Version: 3.1.0.
|
3
|
+
Version: 3.1.0.3
|
4
4
|
Summary: Async client for Embedding Service API with comprehensive authentication, SSL/TLS, and mTLS support
|
5
5
|
Author-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
|
6
6
|
License-Expression: MIT
|
@@ -29,6 +29,7 @@ Requires-Dist: aiohttp>=3.8.0
|
|
29
29
|
Requires-Dist: PyJWT>=2.0.0
|
30
30
|
Requires-Dist: cryptography>=3.0.0
|
31
31
|
Requires-Dist: pydantic>=2.0.0
|
32
|
+
Requires-Dist: mcp-security-framework>=1.2.3
|
32
33
|
Provides-Extra: test
|
33
34
|
Requires-Dist: pytest; extra == "test"
|
34
35
|
Requires-Dist: pytest-asyncio; extra == "test"
|
@@ -1,17 +1,22 @@
|
|
1
1
|
embed_client/__init__.py,sha256=12iI-f075Gmv8IyQ9L7-SH8eNND4qpF6Fjqz1-P4d1Y,535
|
2
|
+
embed_client/__main__.py,sha256=oURC6FcGQxbMzz07-RxZUILIPjjIhMMWyPWEPnfbU4s,235
|
2
3
|
embed_client/async_client.py,sha256=Zf08g-i_2ylDj4tt9E1TO9swi-zcozFDr4mJdlfiGSI,40222
|
3
4
|
embed_client/auth.py,sha256=MMk5XqFIY5htf3Bx0JA_8mJaAiLslpLUIVUaiO84XBw,18310
|
4
5
|
embed_client/auth_examples.py,sha256=QX_QWaaeyBAqvSs9uOP1ZVdAe72gJ91e8AP1e7R-yzQ,7361
|
6
|
+
embed_client/cli.py,sha256=xu8sM1UhnduJFt8Oe-LLxuaCWo6x-ZsYBmZNzP1Fax0,9266
|
5
7
|
embed_client/client_factory.py,sha256=-W2nvzMuyyQNu3_nNmALKHT9uL0Z9nMYTxyQ4bgVOFQ,14279
|
6
8
|
embed_client/client_factory_examples.py,sha256=7A2JWkyGEJeOB7p84ijklh8rTyeRUeg2lCIPTfAtahc,11823
|
7
9
|
embed_client/config.py,sha256=CzVmduJjuN82uiaxDyMyXJLS2tNOOlDBGwcO76SWjXg,19922
|
8
10
|
embed_client/config_examples.py,sha256=9aEF8X4h9nbfSV_CORZLrlWM5KEwuwqW6AcQDPguQ7g,6175
|
11
|
+
embed_client/demo_cli.py,sha256=20MEcpyNLCYIoqSvR1jTROuop3LAQzMdlT-EYGqq2RQ,2052
|
12
|
+
embed_client/demo_security_cli.py,sha256=gwuKzI5R4SzkkMPtkmwIYupHFDec6-yGcgGPYcPBCGI,6932
|
9
13
|
embed_client/example_async_usage.py,sha256=dndf79MIqZaHvWzbtKrZVwU7G4a5yNL1JPMnMBEdLBg,29803
|
10
14
|
embed_client/example_async_usage_ru.py,sha256=3HeP5YoyGd49dRlxNC-2TAh_GbwSW4jJmoLXMpMIfFs,29899
|
15
|
+
embed_client/security_cli.py,sha256=YV_IgT7btVSTMOHPrCnKNCU5NAINfQ5z_eWJSMX0A5I,15114
|
11
16
|
embed_client/ssl_examples.py,sha256=22lTGhK2bqGJ44uUCpc7v2egY4AP3_ar8RMaTb4KBwU,9346
|
12
17
|
embed_client/ssl_manager.py,sha256=Ts6-hKxUUNHdW3yldnoykaDblxWtGbIwKdnzPw__30U,17247
|
13
|
-
embed_client-3.1.0.
|
14
|
-
embed_client-3.1.0.
|
15
|
-
embed_client-3.1.0.
|
16
|
-
embed_client-3.1.0.
|
17
|
-
embed_client-3.1.0.
|
18
|
+
embed_client-3.1.0.3.dist-info/licenses/LICENSE,sha256=6KdtUcTwmTRbJrAmYjVn7e6S-V42ubeDJ-AiVEzZ510,1075
|
19
|
+
embed_client-3.1.0.3.dist-info/METADATA,sha256=15-eyREORjE5HLHyoTcQcs3JYHHEZkIk8veXaXGxE14,8678
|
20
|
+
embed_client-3.1.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
21
|
+
embed_client-3.1.0.3.dist-info/top_level.txt,sha256=uG00A4d9o9DFrhiN7goObpeig72Pniby0E7UpDRgyXY,13
|
22
|
+
embed_client-3.1.0.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|