clsplusplus 4.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.
Files changed (53) hide show
  1. clsplusplus/__init__.py +31 -0
  2. clsplusplus/api.py +1596 -0
  3. clsplusplus/auth.py +74 -0
  4. clsplusplus/cli.py +715 -0
  5. clsplusplus/client.py +462 -0
  6. clsplusplus/config.py +116 -0
  7. clsplusplus/cost_model.py +51 -0
  8. clsplusplus/demo_llm.py +133 -0
  9. clsplusplus/demo_llm_calls.py +100 -0
  10. clsplusplus/demo_local.py +515 -0
  11. clsplusplus/embeddings.py +52 -0
  12. clsplusplus/idempotency.py +66 -0
  13. clsplusplus/integration_service.py +256 -0
  14. clsplusplus/jwt_utils.py +39 -0
  15. clsplusplus/local_routes.py +781 -0
  16. clsplusplus/main.py +21 -0
  17. clsplusplus/memory_cycle.py +216 -0
  18. clsplusplus/memory_phase.py +3541 -0
  19. clsplusplus/memory_service.py +1323 -0
  20. clsplusplus/metrics.py +184 -0
  21. clsplusplus/middleware.py +325 -0
  22. clsplusplus/models.py +430 -0
  23. clsplusplus/permissions.py +54 -0
  24. clsplusplus/plasticity.py +148 -0
  25. clsplusplus/rate_limit.py +53 -0
  26. clsplusplus/rbac_service.py +86 -0
  27. clsplusplus/reconsolidation.py +71 -0
  28. clsplusplus/sleep_cycle.py +109 -0
  29. clsplusplus/stores/__init__.py +13 -0
  30. clsplusplus/stores/base.py +43 -0
  31. clsplusplus/stores/integration_store.py +648 -0
  32. clsplusplus/stores/l0_working_buffer.py +103 -0
  33. clsplusplus/stores/l1_indexing_store.py +427 -0
  34. clsplusplus/stores/l2_schema_graph.py +231 -0
  35. clsplusplus/stores/l3_deep_recess.py +182 -0
  36. clsplusplus/stores/l3_postgres.py +183 -0
  37. clsplusplus/stores/rbac_store.py +327 -0
  38. clsplusplus/stores/user_store.py +255 -0
  39. clsplusplus/stripe_service.py +136 -0
  40. clsplusplus/temporal.py +613 -0
  41. clsplusplus/test_suite.py +587 -0
  42. clsplusplus/tiers.py +109 -0
  43. clsplusplus/tracer.py +226 -0
  44. clsplusplus/usage.py +130 -0
  45. clsplusplus/user_embeddings.py +1636 -0
  46. clsplusplus/user_service.py +256 -0
  47. clsplusplus/webhook_dispatcher.py +229 -0
  48. clsplusplus-4.0.0.dist-info/METADATA +262 -0
  49. clsplusplus-4.0.0.dist-info/RECORD +53 -0
  50. clsplusplus-4.0.0.dist-info/WHEEL +5 -0
  51. clsplusplus-4.0.0.dist-info/entry_points.txt +2 -0
  52. clsplusplus-4.0.0.dist-info/licenses/LICENSE +201 -0
  53. clsplusplus-4.0.0.dist-info/top_level.txt +1 -0
clsplusplus/auth.py ADDED
@@ -0,0 +1,74 @@
1
+ """CLS++ API authentication - secure, constant-time validation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import hmac
7
+ import re
8
+ from typing import Optional
9
+
10
+ from clsplusplus.config import Settings
11
+
12
+
13
+ # API key format: cls_live_* or cls_test_* (min 32 chars total for entropy)
14
+ _API_KEY_PATTERN = re.compile(r"^cls_(?:live|test)_[a-zA-Z0-9]{24,}$")
15
+
16
+
17
+ def _normalize_key(key: Optional[str]) -> Optional[str]:
18
+ """Strip whitespace; return None if empty."""
19
+ if key is None:
20
+ return None
21
+ k = key.strip()
22
+ return k if k else None
23
+
24
+
25
+ def _sha256_hex(s: str) -> str:
26
+ """SHA-256 hex digest of string."""
27
+ return hashlib.sha256(s.encode("utf-8")).hexdigest()
28
+
29
+
30
+ def _get_key_lookup(settings: Settings) -> dict[str, str]:
31
+ """Build hash -> key lookup for constant-time validation."""
32
+ raw = getattr(settings, "api_keys", None) or ""
33
+ if not raw:
34
+ return {}
35
+ lookup: dict[str, str] = {}
36
+ for k in raw.split(","):
37
+ k = _normalize_key(k)
38
+ if k and _API_KEY_PATTERN.match(k):
39
+ lookup[_sha256_hex(k)] = k
40
+ return lookup
41
+
42
+
43
+ def validate_api_key(key: Optional[str], settings: Optional[Settings] = None) -> bool:
44
+ """
45
+ Validate API key using constant-time comparison.
46
+ Hash lookup + single hmac.compare_digest prevents timing attacks.
47
+ """
48
+ if not key:
49
+ return False
50
+ key = _normalize_key(key)
51
+ if not key or not _API_KEY_PATTERN.match(key):
52
+ return False
53
+ settings = settings or Settings()
54
+ lookup = _get_key_lookup(settings)
55
+ if not lookup:
56
+ return False
57
+ key_hash = _sha256_hex(key)
58
+ stored = lookup.get(key_hash)
59
+ return stored is not None and hmac.compare_digest(key, stored)
60
+
61
+
62
+ def extract_bearer_token(auth_header: Optional[str]) -> Optional[str]:
63
+ """Extract Bearer token from Authorization header. Returns None if invalid."""
64
+ if not auth_header or not isinstance(auth_header, str):
65
+ return None
66
+ parts = auth_header.strip().split()
67
+ if len(parts) != 2 or parts[0].lower() != "bearer":
68
+ return None
69
+ return parts[1] if parts[1] else None
70
+
71
+
72
+ def get_api_key_from_request(auth_header: Optional[str]) -> Optional[str]:
73
+ """Extract and return API key from request; None if missing/invalid format."""
74
+ return extract_bearer_token(auth_header)