embed-client-dev 4.0.78__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.
Files changed (48) hide show
  1. embed_client/__init__.py +36 -0
  2. embed_client/__main__.py +15 -0
  3. embed_client/_version.py +3 -0
  4. embed_client/adapter_config_factory.py +213 -0
  5. embed_client/adapter_config_normalizer.py +135 -0
  6. embed_client/adapter_generator_bridge.py +166 -0
  7. embed_client/auth.py +189 -0
  8. embed_client/cli.py +483 -0
  9. embed_client/cli_core.py +427 -0
  10. embed_client/client_factory.py +515 -0
  11. embed_client/client_factory_examples.py +375 -0
  12. embed_client/client_factory_helpers.py +59 -0
  13. embed_client/config.py +408 -0
  14. embed_client/config_defaults.py +77 -0
  15. embed_client/config_examples.py +228 -0
  16. embed_client/config_generator.py +652 -0
  17. embed_client/config_generator_cli.py +151 -0
  18. embed_client/config_presets.py +245 -0
  19. embed_client/config_validator.py +246 -0
  20. embed_client/config_validator_cli.py +141 -0
  21. embed_client/constants.py +52 -0
  22. embed_client/demo_cli.py +78 -0
  23. embed_client/demo_security_cli.py +206 -0
  24. embed_client/embed_file_output_validate.py +348 -0
  25. embed_client/embed_file_status_store.py +80 -0
  26. embed_client/embedding_client.py +1929 -0
  27. embed_client/embedding_client_helpers.py +105 -0
  28. embed_client/example_async_usage.py +357 -0
  29. embed_client/example_async_usage_demo.py +282 -0
  30. embed_client/example_async_usage_ru.py +340 -0
  31. embed_client/example_async_usage_ru_demo.py +313 -0
  32. embed_client/exceptions.py +122 -0
  33. embed_client/identifier_types.py +56 -0
  34. embed_client/json_embed_file_preflight.py +128 -0
  35. embed_client/local_json_embed_file.py +73 -0
  36. embed_client/md_embed_file_prep.py +197 -0
  37. embed_client/response_normalizer.py +160 -0
  38. embed_client/response_parsers.py +219 -0
  39. embed_client/security_cli.py +200 -0
  40. embed_client/security_cli_core.py +227 -0
  41. embed_client/ssl_examples.py +278 -0
  42. embed_client/ssl_manager.py +141 -0
  43. embed_client_dev-4.0.78.dist-info/METADATA +155 -0
  44. embed_client_dev-4.0.78.dist-info/RECORD +48 -0
  45. embed_client_dev-4.0.78.dist-info/WHEEL +5 -0
  46. embed_client_dev-4.0.78.dist-info/entry_points.txt +4 -0
  47. embed_client_dev-4.0.78.dist-info/licenses/LICENSE +21 -0
  48. embed_client_dev-4.0.78.dist-info/top_level.txt +1 -0
@@ -0,0 +1,105 @@
1
+ """
2
+ Helper functions for EmbeddingClient (polling and list_commands).
3
+
4
+ Author: Vasiliy Zdanovskiy
5
+ email: vasilyvz@gmail.com
6
+ """
7
+
8
+ import asyncio
9
+ import time
10
+ from typing import Any, Dict, Optional, Union, cast
11
+
12
+ from embed_client.exceptions import EmbedClientError
13
+
14
+
15
+ def _status_exists(status: Dict[str, Any]) -> bool:
16
+ """Whether a job_status dict names a known job.
17
+
18
+ embed_job_status carries an explicit ``exists`` flag. queue_get_job_status data does not
19
+ (it has ``job_id``/``status``/``result``/``error`` only), so a queue status that names a
20
+ state means the job exists. An empty payload or ``exists: false`` means it is missing.
21
+ """
22
+ if "exists" in status:
23
+ return bool(status.get("exists"))
24
+ return bool(status.get("status"))
25
+
26
+
27
+ def _nested_result_data(inner: Dict[str, Any]) -> Optional[Dict[str, Any]]:
28
+ """Return the first ``data`` dict inside the ``result`` chain of a queue status result.
29
+
30
+ queue_get_job_status keeps the embed envelope one level deeper than embed_job_status:
31
+ ``result = {job_id, command, result: {success, data: <embed result>}}``.
32
+ """
33
+ node: Any = inner.get("result")
34
+ while isinstance(node, dict):
35
+ data = node.get("data")
36
+ if isinstance(data, dict):
37
+ return data
38
+ node = node.get("result")
39
+ return None
40
+
41
+
42
+ async def wait_for_job_poll(
43
+ job_status_fn: Any,
44
+ job_id: str,
45
+ timeout: Union[int, float] = 60,
46
+ poll_interval: float = 1.0,
47
+ ) -> Dict[str, Any]:
48
+ """
49
+ Poll job_status until done or timeout.
50
+
51
+ job_status_fn: async callable(job_id) -> status dict, in either the embed_job_status
52
+ shape (``exists``/``done``) or the queue_get_job_status shape (``status``/``result``).
53
+ Returns result data on completion; raises EmbedClientError or TimeoutError.
54
+ """
55
+ start_time = time.time()
56
+ while timeout == 0 or (time.time() - start_time < timeout):
57
+ status = await job_status_fn(job_id)
58
+ if not _status_exists(status):
59
+ raise EmbedClientError(f"Job {job_id} not found")
60
+ done = status.get("done", False) or status.get("status") in (
61
+ "completed",
62
+ "failed",
63
+ "error",
64
+ )
65
+ if done:
66
+ job_status = status.get("status", "")
67
+ if job_status in ("completed", "success"):
68
+ inner = status.get("result") or status.get("data") or status
69
+ if isinstance(inner, dict):
70
+ data = inner.get("data")
71
+ if data is not None:
72
+ return cast(Dict[str, Any], data)
73
+ nested = _nested_result_data(inner)
74
+ if nested is not None:
75
+ return cast(Dict[str, Any], nested)
76
+ return cast(Dict[str, Any], inner if isinstance(inner, dict) else {})
77
+ if job_status in ("failed", "error"):
78
+ error = status.get("error") or status.get("message", "Unknown error")
79
+ raise EmbedClientError(
80
+ f"Job failed: {error}",
81
+ error=status.get("error") or status.get("message"),
82
+ )
83
+ raise EmbedClientError(
84
+ f"Job completed with unexpected status: {job_status}"
85
+ )
86
+ await asyncio.sleep(poll_interval)
87
+ raise TimeoutError(f"Job {job_id} did not complete within {timeout} seconds")
88
+
89
+
90
+ async def list_commands_via_client(jsonrpc_call: Any) -> Dict[str, Any]:
91
+ """
92
+ Call list/help/commands via jsonrpc_call(cmd_name, {}); return data or {}.
93
+ """
94
+ for cmd_name in ["list", "help", "commands"]:
95
+ try:
96
+ response = await jsonrpc_call(cmd_name, {})
97
+ if "error" not in response:
98
+ result = response.get("result", {})
99
+ return cast(Dict[str, Any], result.get("data", result))
100
+ except Exception:
101
+ continue
102
+ return {}
103
+
104
+
105
+ __all__ = ["wait_for_job_poll", "list_commands_via_client"]
@@ -0,0 +1,357 @@
1
+ """
2
+ Embedding client example — :class:`~embed_client.embedding_client.EmbeddingClient` with
3
+ ``ClientFactory`` and CLI-driven config.
4
+
5
+ Author: Vasiliy Zdanovskiy
6
+ email: vasilyvz@gmail.com
7
+
8
+ This script shows how to build configuration from CLI arguments, files, and environment
9
+ variables; construct clients for security modes via ``ClientFactory``; and run
10
+ demonstration flows with ``--demo-mode``.
11
+
12
+ **SEE ALSO:** ``docs/client_integration_guide.md``, ``README.md``,
13
+ ``examples/canonical_mtls_localhost_8001.py`` (canonical localhost:8001 mTLS tour).
14
+
15
+ **ENVIRONMENT:** respects the same ``EMBED_CLIENT_*`` variables as ``ClientConfig`` when loading.
16
+
17
+ **EXIT STATUS:** ``0`` on success; ``1`` on configuration or unexpected errors (see ``main()``).
18
+ """
19
+
20
+ import argparse
21
+ import asyncio
22
+ import json
23
+ import os
24
+ import sys
25
+ from typing import Any, Dict
26
+
27
+ from embed_client.embedding_client import EmbeddingClient
28
+ from embed_client.exceptions import EmbedConfigError
29
+ from embed_client.config import ClientConfig
30
+ from embed_client.client_factory import ClientFactory, create_client
31
+ from embed_client.example_async_usage_demo import (
32
+ run_client_examples,
33
+ demonstrate_security_modes,
34
+ demonstrate_automatic_detection,
35
+ demonstrate_with_auth_method,
36
+ )
37
+
38
+
39
+ def get_params() -> Any:
40
+ """Parse command line arguments and environment variables for client configuration."""
41
+ parser = argparse.ArgumentParser(
42
+ description="EmbeddingClient example — all security modes (CLI + demo)"
43
+ )
44
+
45
+ # Basic connection parameters
46
+ parser.add_argument("--base-url", "-b", help="Base URL of the embedding service")
47
+ parser.add_argument("--port", "-p", type=int, help="Port of the embedding service")
48
+ parser.add_argument("--config", "-c", help="Path to configuration file")
49
+
50
+ # Client factory mode
51
+ parser.add_argument(
52
+ "--factory-mode",
53
+ choices=[
54
+ "auto",
55
+ "http",
56
+ "http_token",
57
+ "https",
58
+ "https_token",
59
+ "mtls",
60
+ "mtls_roles",
61
+ ],
62
+ default="auto",
63
+ help="Client factory mode (auto for automatic detection)",
64
+ )
65
+
66
+ # Authentication parameters
67
+ parser.add_argument(
68
+ "--auth-method",
69
+ choices=["none", "api_key", "jwt", "basic", "certificate"],
70
+ default="none",
71
+ help="Authentication method",
72
+ )
73
+ parser.add_argument("--api-key", help="API key for api_key authentication")
74
+ parser.add_argument("--jwt-secret", help="JWT secret for jwt authentication")
75
+ parser.add_argument("--jwt-username", help="JWT username for jwt authentication")
76
+ parser.add_argument("--jwt-password", help="JWT password for jwt authentication")
77
+ parser.add_argument("--username", help="Username for basic authentication")
78
+ parser.add_argument("--password", help="Password for basic authentication")
79
+ parser.add_argument(
80
+ "--cert-file", help="Certificate file for certificate authentication"
81
+ )
82
+ parser.add_argument("--key-file", help="Key file for certificate authentication")
83
+
84
+ # SSL/TLS parameters
85
+ parser.add_argument(
86
+ "--ssl-verify-mode",
87
+ choices=["CERT_NONE", "CERT_OPTIONAL", "CERT_REQUIRED"],
88
+ default="CERT_REQUIRED",
89
+ help="SSL certificate verification mode",
90
+ )
91
+ parser.add_argument(
92
+ "--ssl-check-hostname",
93
+ action="store_true",
94
+ default=True,
95
+ help="Enable SSL hostname checking",
96
+ )
97
+ parser.add_argument(
98
+ "--ssl-check-expiry",
99
+ action="store_true",
100
+ default=True,
101
+ help="Enable SSL certificate expiry checking",
102
+ )
103
+ parser.add_argument(
104
+ "--ca-cert-file", help="CA certificate file for SSL verification"
105
+ )
106
+
107
+ # Role-based access control (for mTLS + Roles)
108
+ parser.add_argument(
109
+ "--roles", help="Comma-separated list of roles for mTLS + Roles mode"
110
+ )
111
+ parser.add_argument(
112
+ "--role-attributes", help="JSON string of role attributes for mTLS + Roles mode"
113
+ )
114
+
115
+ # Additional parameters
116
+ parser.add_argument(
117
+ "--timeout", type=float, default=30.0, help="Request timeout in seconds"
118
+ )
119
+ parser.add_argument(
120
+ "--demo-mode",
121
+ action="store_true",
122
+ help="Run in demo mode (show all security modes)",
123
+ )
124
+
125
+ args = parser.parse_args()
126
+
127
+ # Store demo_mode in args for later use
128
+ args.demo_mode = args.demo_mode
129
+
130
+ # If demo mode is requested, return args directly
131
+ if args.demo_mode:
132
+ return args
133
+
134
+ # If config file is provided, load it
135
+ if args.config:
136
+ try:
137
+ config = ClientConfig()
138
+ config.load_from_file(args.config)
139
+ return config
140
+ except Exception as e:
141
+ print(f"Error loading config file {args.config}: {e}")
142
+ sys.exit(1)
143
+
144
+ # Otherwise, build config from arguments and environment variables
145
+ base_url = args.base_url or os.environ.get(
146
+ "EMBED_CLIENT_BASE_URL", "http://localhost"
147
+ )
148
+ port = args.port or int(os.environ.get("EMBED_CLIENT_PORT", "8001"))
149
+
150
+ if not base_url or not port:
151
+ print(
152
+ "Error: base_url and port must be provided via --base-url/--port "
153
+ "arguments or EMBED_CLIENT_BASE_URL/EMBED_CLIENT_PORT "
154
+ "environment variables."
155
+ )
156
+ sys.exit(1)
157
+
158
+ # Build configuration dictionary
159
+ config_dict: Dict[str, Any] = {
160
+ "server": {"host": base_url, "port": port},
161
+ "client": {"timeout": args.timeout},
162
+ "auth": {"method": args.auth_method},
163
+ }
164
+
165
+ # Add authentication configuration
166
+ if args.auth_method == "api_key":
167
+ api_key = args.api_key or os.environ.get("EMBED_CLIENT_API_KEY")
168
+ if api_key:
169
+ config_dict["auth"]["api_keys"] = {"user": api_key}
170
+ else:
171
+ print("Warning: API key not provided for api_key authentication")
172
+
173
+ elif args.auth_method == "jwt":
174
+ jwt_secret = args.jwt_secret or os.environ.get("EMBED_CLIENT_JWT_SECRET")
175
+ jwt_username = args.jwt_username or os.environ.get("EMBED_CLIENT_JWT_USERNAME")
176
+ jwt_password = args.jwt_password or os.environ.get("EMBED_CLIENT_JWT_PASSWORD")
177
+
178
+ if jwt_secret and jwt_username and jwt_password:
179
+ config_dict["auth"]["jwt"] = {
180
+ "secret": jwt_secret,
181
+ "username": jwt_username,
182
+ "password": jwt_password,
183
+ }
184
+ else:
185
+ print("Warning: JWT credentials not fully provided")
186
+
187
+ elif args.auth_method == "basic":
188
+ username = args.username or os.environ.get("EMBED_CLIENT_USERNAME")
189
+ password = args.password or os.environ.get("EMBED_CLIENT_PASSWORD")
190
+
191
+ if username and password:
192
+ config_dict["auth"]["basic"] = {"username": username, "password": password}
193
+ else:
194
+ print("Warning: Basic auth credentials not fully provided")
195
+
196
+ elif args.auth_method == "certificate":
197
+ cert_file = args.cert_file or os.environ.get("EMBED_CLIENT_CERT_FILE")
198
+ key_file = args.key_file or os.environ.get("EMBED_CLIENT_KEY_FILE")
199
+
200
+ if cert_file and key_file:
201
+ config_dict["auth"]["certificate"] = {
202
+ "cert_file": cert_file,
203
+ "key_file": key_file,
204
+ }
205
+ else:
206
+ print("Warning: Certificate files not fully provided")
207
+
208
+ # Add SSL configuration if HTTPS is used or SSL parameters are provided
209
+ if (
210
+ base_url.startswith("https://")
211
+ or args.ssl_verify_mode != "CERT_REQUIRED"
212
+ or args.ca_cert_file
213
+ ):
214
+ # Force check_hostname=False for CERT_NONE mode
215
+ check_hostname = args.ssl_check_hostname
216
+ if args.ssl_verify_mode == "CERT_NONE":
217
+ check_hostname = False
218
+
219
+ config_dict["ssl"] = {
220
+ "enabled": True,
221
+ "verify_mode": args.ssl_verify_mode,
222
+ "check_hostname": check_hostname,
223
+ "check_expiry": args.ssl_check_expiry,
224
+ }
225
+
226
+ if args.ca_cert_file:
227
+ config_dict["ssl"]["ca_cert_file"] = args.ca_cert_file
228
+
229
+ # Add client certificates for mTLS
230
+ if args.cert_file:
231
+ config_dict["ssl"]["cert_file"] = args.cert_file
232
+ if args.key_file:
233
+ config_dict["ssl"]["key_file"] = args.key_file
234
+
235
+ # Add role-based access control for mTLS + Roles
236
+ if args.roles:
237
+ roles = [role.strip() for role in args.roles.split(",")]
238
+ config_dict["roles"] = roles
239
+
240
+ if args.role_attributes:
241
+ try:
242
+ role_attributes = json.loads(args.role_attributes)
243
+ config_dict["role_attributes"] = role_attributes
244
+ except json.JSONDecodeError:
245
+ print("Warning: Invalid JSON in role_attributes")
246
+
247
+ return config_dict
248
+
249
+
250
+ async def main():
251
+ try:
252
+ config = get_params()
253
+
254
+ # Check if demo mode is requested
255
+ if hasattr(config, "demo_mode") and config.demo_mode:
256
+ await demonstrate_security_modes()
257
+ await demonstrate_automatic_detection()
258
+ await demonstrate_with_auth_method()
259
+ return
260
+
261
+ # Create client based on factory mode
262
+ if isinstance(config, ClientConfig):
263
+ # Using configuration object
264
+ client = EmbeddingClient.from_config(config)
265
+ else:
266
+ # Using configuration dictionary
267
+ factory_mode = getattr(config, "factory_mode", "auto")
268
+
269
+ if factory_mode == "auto":
270
+ # Automatic detection
271
+ client = create_client(
272
+ config["server"]["host"],
273
+ config["server"]["port"],
274
+ auth_method=config["auth"]["method"],
275
+ **{
276
+ k: v
277
+ for k, v in config.items()
278
+ if k not in ["server", "auth", "ssl", "client"]
279
+ },
280
+ )
281
+ else:
282
+ # Specific factory method
283
+ base_url = config["server"]["host"]
284
+ port = config["server"]["port"]
285
+ auth_method = config["auth"]["method"]
286
+
287
+ if factory_mode == "http":
288
+ client = ClientFactory.create_http_client(base_url, port)
289
+ elif factory_mode == "http_token":
290
+ client = ClientFactory.create_http_token_client(
291
+ base_url, port, auth_method, **config.get("auth", {})
292
+ )
293
+ elif factory_mode == "https":
294
+ client = ClientFactory.create_https_client(base_url, port)
295
+ elif factory_mode == "https_token":
296
+ client = ClientFactory.create_https_token_client(
297
+ base_url, port, auth_method, **config.get("auth", {})
298
+ )
299
+ elif factory_mode == "mtls":
300
+ cert_file = config.get("ssl", {}).get(
301
+ "cert_file", "client_cert.pem"
302
+ )
303
+ key_file = config.get("ssl", {}).get("key_file", "client_key.pem")
304
+ client = ClientFactory.create_mtls_client(
305
+ base_url, cert_file, key_file, port
306
+ )
307
+ elif factory_mode == "mtls_roles":
308
+ cert_file = config.get("ssl", {}).get(
309
+ "cert_file", "client_cert.pem"
310
+ )
311
+ key_file = config.get("ssl", {}).get("key_file", "client_key.pem")
312
+ roles = config.get("roles", ["admin"])
313
+ role_attributes = config.get("role_attributes", {})
314
+ client = ClientFactory.create_mtls_roles_client(
315
+ base_url, cert_file, key_file, port, roles, role_attributes
316
+ )
317
+ else:
318
+ client = EmbeddingClient.from_config_dict(config)
319
+
320
+ print("Client configuration:")
321
+ print(f" Base URL: {client.base_url}")
322
+ print(f" Port: {client.port}")
323
+ print(f" Authentication: {client.get_auth_method()}")
324
+ print(f" Authenticated: {client.is_authenticated()}")
325
+ if client.is_authenticated():
326
+ headers = client.get_auth_headers()
327
+ print(f" Auth headers: {headers}")
328
+ print(f" SSL enabled: {client.is_ssl_enabled()}")
329
+ print(f" mTLS enabled: {client.is_mtls_enabled()}")
330
+ if client.is_ssl_enabled():
331
+ ssl_config = client.get_ssl_config()
332
+ print(f" SSL config: {ssl_config}")
333
+ print()
334
+
335
+ # Explicit open/close example
336
+ print("Explicit session open/close example:")
337
+ await client.close()
338
+ print("Session closed explicitly (manual close example).\n")
339
+
340
+ # Use context manager
341
+ if isinstance(config, ClientConfig):
342
+ async with EmbeddingClient.from_config(config) as client:
343
+ await run_client_examples(client)
344
+ else:
345
+ async with EmbeddingClient.from_config_dict(config) as client:
346
+ await run_client_examples(client)
347
+
348
+ except EmbedConfigError as e:
349
+ print(f"Configuration error: {e}")
350
+ sys.exit(1)
351
+ except Exception as e:
352
+ print(f"Unexpected error: {e}")
353
+ sys.exit(1)
354
+
355
+
356
+ if __name__ == "__main__":
357
+ asyncio.run(main())