cluebase-backend-sdk 0.0.1__tar.gz

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 (106) hide show
  1. cluebase_backend_sdk-0.0.1/PKG-INFO +193 -0
  2. cluebase_backend_sdk-0.0.1/README.md +136 -0
  3. cluebase_backend_sdk-0.0.1/pyproject.toml +180 -0
  4. cluebase_backend_sdk-0.0.1/setup.cfg +4 -0
  5. cluebase_backend_sdk-0.0.1/setup.py +20 -0
  6. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/__init__.py +484 -0
  7. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_cluebase_authored_attributes.py +37 -0
  8. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_generated/__init__.py +11 -0
  9. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_generated/correlation_attribute_keys.py +37 -0
  10. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_generated/identity_pii_policy.py +62 -0
  11. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_generated/ingest_privacy_tables.py +727 -0
  12. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_generated/otel_resource_keys.py +62 -0
  13. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_generated/sdk_hint_vocabulary.py +54 -0
  14. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_generated/usage_governor_tables.py +52 -0
  15. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_generated/wire_credential_policy.py +24 -0
  16. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_init_options.py +156 -0
  17. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/__init__.py +10 -0
  18. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/ai/__init__.py +10 -0
  19. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/ai/_response_parsing.py +144 -0
  20. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/ai/_span_attributes.py +249 -0
  21. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/ai/_version.py +5 -0
  22. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/ai/instrumentation/__init__.py +1 -0
  23. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/ai/instrumentation/anthropic.py +526 -0
  24. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/ai/instrumentation/base.py +543 -0
  25. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/ai/instrumentation/openai.py +250 -0
  26. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/ai/observer.py +774 -0
  27. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/crewai/__init__.py +29 -0
  28. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/crewai/observer.py +393 -0
  29. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/django/__init__.py +43 -0
  30. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/django/django.py +85 -0
  31. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/django/install.py +154 -0
  32. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/fastapi/__init__.py +35 -0
  33. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/fastapi/fastapi.py +432 -0
  34. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/langchain/__init__.py +28 -0
  35. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/langchain/_run_tracker.py +127 -0
  36. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/langchain/callback.py +505 -0
  37. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/langchain/instrument.py +123 -0
  38. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/llamaindex/__init__.py +28 -0
  39. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/llamaindex/_run_tracker.py +124 -0
  40. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/llamaindex/event_handler.py +320 -0
  41. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/llamaindex/instrument.py +93 -0
  42. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/mcp/__init__.py +14 -0
  43. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/mcp/_method_observations.py +569 -0
  44. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/mcp/_observer_helpers.py +530 -0
  45. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/mcp/_tool_observation.py +346 -0
  46. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/mcp/middleware.py +103 -0
  47. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/mcp/observer.py +660 -0
  48. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_integrations/mcp/session.py +105 -0
  49. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_internal_log.py +51 -0
  50. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_safe_logger.py +32 -0
  51. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/_version.py +5 -0
  52. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/context/__init__.py +9 -0
  53. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/context/parameter_snapshot.py +183 -0
  54. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/__init__.py +10 -0
  55. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/client.py +574 -0
  56. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/collection_gate.py +217 -0
  57. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/contracts.py +577 -0
  58. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/deadline.py +36 -0
  59. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/delivery.py +509 -0
  60. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/delivery_runner.py +148 -0
  61. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/diagnostics.py +216 -0
  62. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/envelope.py +60 -0
  63. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/event_id.py +74 -0
  64. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/event_size.py +98 -0
  65. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/http_transport.py +262 -0
  66. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/lifecycle.py +91 -0
  67. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/observation_source_event_exporter.py +213 -0
  68. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/otel_bridge.py +192 -0
  69. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/pending.py +394 -0
  70. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/privacy.py +313 -0
  71. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/profile.py +149 -0
  72. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/sanitize.py +39 -0
  73. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/source_signal.py +299 -0
  74. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/source_signal_context.py +53 -0
  75. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/source_signal_privacy.py +88 -0
  76. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/source_signal_record_privacy.py +193 -0
  77. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/exporter/wire_credentials.py +33 -0
  78. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/instrumentation/__init__.py +10 -0
  79. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/instrumentation/_orm_django.py +188 -0
  80. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/instrumentation/_orm_spans.py +271 -0
  81. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/instrumentation/_orm_sqlalchemy.py +337 -0
  82. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/instrumentation/_orm_state.py +100 -0
  83. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/instrumentation/celery.py +539 -0
  84. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/instrumentation/http_extraction.py +158 -0
  85. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/instrumentation/httpx.py +101 -0
  86. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/instrumentation/orm.py +44 -0
  87. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/instrumentation/requests.py +254 -0
  88. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/instrumentation/server_request.py +104 -0
  89. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/instrumentation/span_attributes.py +71 -0
  90. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/provider/__init__.py +10 -0
  91. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/provider/_pipeline.py +286 -0
  92. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/provider/_runtime_state.py +691 -0
  93. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/provider/_runtime_telemetry.py +873 -0
  94. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/provider/bootstrap.py +179 -0
  95. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/provider/consent_span_processor.py +103 -0
  96. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/provider/privacy_policy_store.py +115 -0
  97. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/provider/resources.py +96 -0
  98. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/provider/runtime.py +258 -0
  99. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/provider/runtime_context.py +43 -0
  100. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/provider/runtime_diagnostics.py +66 -0
  101. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk/provider/runtime_lifecycle.py +74 -0
  102. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk.egg-info/PKG-INFO +193 -0
  103. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk.egg-info/SOURCES.txt +104 -0
  104. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk.egg-info/dependency_links.txt +1 -0
  105. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk.egg-info/requires.txt +58 -0
  106. cluebase_backend_sdk-0.0.1/src/cluebase_backend_sdk.egg-info/top_level.txt +1 -0
@@ -0,0 +1,193 @@
1
+ Metadata-Version: 2.4
2
+ Name: cluebase-backend-sdk
3
+ Version: 0.0.1
4
+ Summary: Cluebase Backend SDK (Python) — OTel-based observation-source event capture for Django, FastAPI, Celery, SQLAlchemy, LangChain, LlamaIndex, CrewAI, MCP, and AI providers.
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: opentelemetry-api<2.0,>=1.41.1
8
+ Requires-Dist: opentelemetry-sdk<2.0,>=1.41.1
9
+ Requires-Dist: rfc8785<0.2,>=0.1.4
10
+ Provides-Extra: mutation
11
+ Requires-Dist: mutmut<4.0,>=3.2; extra == "mutation"
12
+ Provides-Extra: instrumentation-requests
13
+ Requires-Dist: opentelemetry-instrumentation-requests<1.0,>=0.63b1; extra == "instrumentation-requests"
14
+ Provides-Extra: instrumentation-httpx
15
+ Requires-Dist: opentelemetry-instrumentation-httpx<1.0,>=0.63b1; extra == "instrumentation-httpx"
16
+ Provides-Extra: instrumentation-fastapi
17
+ Requires-Dist: fastapi<1.0,>=0.100; extra == "instrumentation-fastapi"
18
+ Requires-Dist: opentelemetry-instrumentation-fastapi<1.0,>=0.63b1; extra == "instrumentation-fastapi"
19
+ Provides-Extra: instrumentation-asgi
20
+ Requires-Dist: opentelemetry-instrumentation-asgi<1.0,>=0.63b1; extra == "instrumentation-asgi"
21
+ Provides-Extra: instrumentation-sqlalchemy
22
+ Requires-Dist: opentelemetry-instrumentation-sqlalchemy<1.0,>=0.63b1; extra == "instrumentation-sqlalchemy"
23
+ Provides-Extra: instrumentation-django
24
+ Requires-Dist: opentelemetry-instrumentation-django<1.0,>=0.63b1; extra == "instrumentation-django"
25
+ Provides-Extra: instrumentation-celery
26
+ Requires-Dist: opentelemetry-instrumentation-celery<1.0,>=0.63b1; extra == "instrumentation-celery"
27
+ Provides-Extra: test
28
+ Requires-Dist: pytest<9.0,>=8.0; extra == "test"
29
+ Requires-Dist: pytest-cov<7.0,>=5.0; extra == "test"
30
+ Requires-Dist: pyright<1.2,>=1.1.411; extra == "test"
31
+ Requires-Dist: build<2.0,>=1.2; extra == "test"
32
+ Requires-Dist: packaging<26,>=25; extra == "test"
33
+ Requires-Dist: twine<7.0,>=6.1; extra == "test"
34
+ Requires-Dist: opentelemetry-instrumentation-requests<1.0,>=0.63b1; extra == "test"
35
+ Requires-Dist: opentelemetry-instrumentation-httpx<1.0,>=0.63b1; extra == "test"
36
+ Requires-Dist: opentelemetry-instrumentation-fastapi<1.0,>=0.63b1; extra == "test"
37
+ Requires-Dist: opentelemetry-instrumentation-asgi<1.0,>=0.63b1; extra == "test"
38
+ Requires-Dist: opentelemetry-instrumentation-sqlalchemy<1.0,>=0.63b1; extra == "test"
39
+ Requires-Dist: opentelemetry-instrumentation-django<1.0,>=0.63b1; extra == "test"
40
+ Requires-Dist: opentelemetry-instrumentation-celery<1.0,>=0.63b1; extra == "test"
41
+ Requires-Dist: requests>=2.31; extra == "test"
42
+ Requires-Dist: httpx>=0.26; extra == "test"
43
+ Requires-Dist: sqlalchemy>=2.0; extra == "test"
44
+ Requires-Dist: django<6.0,>=5.0; extra == "test"
45
+ Requires-Dist: fastapi<1.0,>=0.100; extra == "test"
46
+ Requires-Dist: celery<6.0,>=5.0; extra == "test"
47
+ Requires-Dist: langchain-core<1.0,>=0.3; extra == "test"
48
+ Requires-Dist: llama-index-core<1.0,>=0.12; extra == "test"
49
+ Requires-Dist: crewai<1.16,>=1.15.1; extra == "test"
50
+ Requires-Dist: openai==2.45.0; extra == "test"
51
+ Requires-Dist: anthropic==0.116.0; extra == "test"
52
+ Requires-Dist: opentelemetry-instrumentation-openai==0.62.1; extra == "test"
53
+ Requires-Dist: opentelemetry-instrumentation-anthropic==0.62.1; extra == "test"
54
+ Requires-Dist: aiosqlite==0.21.0; extra == "test"
55
+ Requires-Dist: setuptools<83,>=68; extra == "test"
56
+ Dynamic: requires-python
57
+
58
+ # cluebase-backend-sdk
59
+
60
+ Shared Python backend core for Cluebase SDK wrappers (Django, FastAPI,
61
+ Celery). Events flow through an OTel `TracerProvider` + `ConsentSpanProcessor` +
62
+ `ObservationSourceEventExporter` and the shared delivery queue to
63
+ `/api/v1/ingest/backend`.
64
+
65
+ ## Minimal integration
66
+
67
+ The SDK exposes a single ``cluebase`` namespace with eight methods (``init`` /
68
+ ``identify`` / ``group`` / ``reset`` / ``track`` / ``set_consent`` /
69
+ ``flush`` / ``close``). This mirrors the cross-SDK contract documented in
70
+ ``docs/contracts/sdk/public-api.md`` §1.6.
71
+
72
+ ```python
73
+ import asyncio
74
+ from cluebase_backend_sdk import cluebase
75
+
76
+ cluebase.init({
77
+ "endpoint": "https://api.example.com",
78
+ "project_key": "...",
79
+ "api_key": "...",
80
+ "service_key": "backend-api",
81
+ })
82
+
83
+ cluebase.identify("user_42", {"name": "Alice", "email": "alice@example.com"})
84
+ cluebase.group("organization", "org_7", {"name": "Acme Inc"})
85
+ cluebase.track("order_placed", {"product_name": "shirt", "amount": 100})
86
+ cluebase.set_consent("granted")
87
+
88
+ # Shutdown idiom — both are coroutines for cross-SDK parity.
89
+ asyncio.run(cluebase.flush())
90
+ asyncio.run(cluebase.close())
91
+ ```
92
+
93
+ Do not pass ``environment`` or ``service_name`` in the MVP setup path.
94
+ Cluebase derives dev/prod from the project key prefix, and ``service_key`` is the
95
+ single literal customer-facing backend service label.
96
+
97
+ ``cluebase.track`` custom event names must match
98
+ ``^[A-Za-z0-9_.:-]{1,128}$``. Invalid names are ignored and do not emit
99
+ a source-signal custom track intent. ``cluebase.set_consent`` only records local
100
+ SDK state in the MVP backend SDK; it does not emit consent transition events.
101
+
102
+ Framework wrappers and runtime helpers are imported from their owning modules.
103
+ The package root is intentionally small: customer-facing lifecycle methods plus
104
+ stable setup helpers. Direct event builders are not public extension
105
+ points.
106
+
107
+ ## Architecture boundary
108
+
109
+ The Python SDK is a thin language wrapper around OpenTelemetry and the Cluebase
110
+ transport. Framework integrations create spans or annotate the current span,
111
+ the exporter serializes those spans as ``sdk_source_signal_observed`` events,
112
+ and the Cluebase backend ingest core performs classification, normalization,
113
+ privacy projection, and raw-ingest shaping.
114
+
115
+ ```text
116
+ Python framework / library hook
117
+ -> OTel span or Cluebase source-signal attributes
118
+ -> ObservationSourceEventExporter
119
+ -> /api/v1/ingest/backend
120
+ -> apps/api/src/modules/ingest core
121
+ ```
122
+
123
+ Rules:
124
+
125
+ - Keep framework code limited to raw facts, correlation context, and standard
126
+ OTel attributes.
127
+ - Do not add Python-only classification, direct event builders, or custom
128
+ fallback behavior.
129
+ - Add classifier behavior in the backend ingest core so Node.js, Python, and
130
+ future SDK wrappers receive the same behavior.
131
+ - Preserve ``interaction_id``, ``request_span_id``, ``request_id``, and
132
+ ``trace_id`` whenever they are available. User-action linkage is an ingest
133
+ contract, not a display-only field.
134
+
135
+ ## Privacy and PII handling
136
+
137
+ ### 1. Hard-deny: PII / secrets are stripped before transport
138
+
139
+ The SDK strips a built-in set of property keys **before** the event
140
+ leaves the customer process. Caller-supplied `denied_keys` are added
141
+ on top of `DEFAULT_DENIED_KEYS` — they cannot remove a default-denied
142
+ key.
143
+
144
+ Hard-deny categories (case- and separator-insensitive — `userEmail`,
145
+ `user-email`, `USER_EMAIL`, `email_address` all match):
146
+
147
+ - Auth credentials: `authorization`, `cookie`, `set-cookie`,
148
+ `password`, `passwd`, `secret`, `token`, `access_token`,
149
+ `refresh_token`, `session`, `session_token`, `api_key`, `apikey`,
150
+ `private_key`
151
+ - PII categories: `email`, `phone`, `credit_card`, `ssn`
152
+
153
+ This list is a strict superset of the server-side ingest hard-deny
154
+ (`@cluebase/shared` `INGEST_HARD_DENY_KEYS`), enforced by the
155
+ `tests/test_privacy_parity.py` cross-SDK parity test.
156
+
157
+ ### 2. HTTP request values
158
+
159
+ This SDK does not read HTTP request or response bodies. The value surface it can
160
+ observe is the request query, and its values are masked by default. A value is
161
+ sent in plaintext only when the customer names that field on the Cluebase screen; the
162
+ decision is delivered to the SDK and re-checked by ingest. Hard-denied keys such
163
+ as `email` can never be released, whatever the screen says. See
164
+ `docs/arch/sdk/backend-sdk.md` for the boundary and for what would be required to
165
+ bring bodies into scope.
166
+
167
+ ### 3. Analysis projection and later registration
168
+
169
+ Properties that pass the hard-deny gate are projected by default unless a
170
+ cardinality, free-text, or complex-value guard keeps them out of the analysis
171
+ index. Guarded non-private values remain available in
172
+ `analysisProperties.raw_only` so they can be registered and promoted later.
173
+ The map stores each original value as JSON so its type can be restored.
174
+ Hard-denied values are dropped and never stored in `raw_only`. After a privacy
175
+ review, use the project service allowlist to promote a guarded non-private
176
+ property into the analysis projection.
177
+
178
+ ### 4. Identity traits stay privacy-safe
179
+
180
+ `cluebase.identify(user_id, traits={...})` accepts profile traits such as
181
+ `name`, `email`, and `avatarUrl`. Other customer-defined values remain generic
182
+ subject traits instead of becoming fixed Cluebase profile fields. Use a stable opaque
183
+ `user_id`; the SDK converts `email` into privacy-safe contact-derived
184
+ fields before canonical identity events leave the process.
185
+
186
+ ## Build / test
187
+
188
+ ```sh
189
+ python3 -m venv .venv
190
+ source .venv/bin/activate
191
+ pip install -e '.[test]'
192
+ python -m pytest
193
+ ```
@@ -0,0 +1,136 @@
1
+ # cluebase-backend-sdk
2
+
3
+ Shared Python backend core for Cluebase SDK wrappers (Django, FastAPI,
4
+ Celery). Events flow through an OTel `TracerProvider` + `ConsentSpanProcessor` +
5
+ `ObservationSourceEventExporter` and the shared delivery queue to
6
+ `/api/v1/ingest/backend`.
7
+
8
+ ## Minimal integration
9
+
10
+ The SDK exposes a single ``cluebase`` namespace with eight methods (``init`` /
11
+ ``identify`` / ``group`` / ``reset`` / ``track`` / ``set_consent`` /
12
+ ``flush`` / ``close``). This mirrors the cross-SDK contract documented in
13
+ ``docs/contracts/sdk/public-api.md`` §1.6.
14
+
15
+ ```python
16
+ import asyncio
17
+ from cluebase_backend_sdk import cluebase
18
+
19
+ cluebase.init({
20
+ "endpoint": "https://api.example.com",
21
+ "project_key": "...",
22
+ "api_key": "...",
23
+ "service_key": "backend-api",
24
+ })
25
+
26
+ cluebase.identify("user_42", {"name": "Alice", "email": "alice@example.com"})
27
+ cluebase.group("organization", "org_7", {"name": "Acme Inc"})
28
+ cluebase.track("order_placed", {"product_name": "shirt", "amount": 100})
29
+ cluebase.set_consent("granted")
30
+
31
+ # Shutdown idiom — both are coroutines for cross-SDK parity.
32
+ asyncio.run(cluebase.flush())
33
+ asyncio.run(cluebase.close())
34
+ ```
35
+
36
+ Do not pass ``environment`` or ``service_name`` in the MVP setup path.
37
+ Cluebase derives dev/prod from the project key prefix, and ``service_key`` is the
38
+ single literal customer-facing backend service label.
39
+
40
+ ``cluebase.track`` custom event names must match
41
+ ``^[A-Za-z0-9_.:-]{1,128}$``. Invalid names are ignored and do not emit
42
+ a source-signal custom track intent. ``cluebase.set_consent`` only records local
43
+ SDK state in the MVP backend SDK; it does not emit consent transition events.
44
+
45
+ Framework wrappers and runtime helpers are imported from their owning modules.
46
+ The package root is intentionally small: customer-facing lifecycle methods plus
47
+ stable setup helpers. Direct event builders are not public extension
48
+ points.
49
+
50
+ ## Architecture boundary
51
+
52
+ The Python SDK is a thin language wrapper around OpenTelemetry and the Cluebase
53
+ transport. Framework integrations create spans or annotate the current span,
54
+ the exporter serializes those spans as ``sdk_source_signal_observed`` events,
55
+ and the Cluebase backend ingest core performs classification, normalization,
56
+ privacy projection, and raw-ingest shaping.
57
+
58
+ ```text
59
+ Python framework / library hook
60
+ -> OTel span or Cluebase source-signal attributes
61
+ -> ObservationSourceEventExporter
62
+ -> /api/v1/ingest/backend
63
+ -> apps/api/src/modules/ingest core
64
+ ```
65
+
66
+ Rules:
67
+
68
+ - Keep framework code limited to raw facts, correlation context, and standard
69
+ OTel attributes.
70
+ - Do not add Python-only classification, direct event builders, or custom
71
+ fallback behavior.
72
+ - Add classifier behavior in the backend ingest core so Node.js, Python, and
73
+ future SDK wrappers receive the same behavior.
74
+ - Preserve ``interaction_id``, ``request_span_id``, ``request_id``, and
75
+ ``trace_id`` whenever they are available. User-action linkage is an ingest
76
+ contract, not a display-only field.
77
+
78
+ ## Privacy and PII handling
79
+
80
+ ### 1. Hard-deny: PII / secrets are stripped before transport
81
+
82
+ The SDK strips a built-in set of property keys **before** the event
83
+ leaves the customer process. Caller-supplied `denied_keys` are added
84
+ on top of `DEFAULT_DENIED_KEYS` — they cannot remove a default-denied
85
+ key.
86
+
87
+ Hard-deny categories (case- and separator-insensitive — `userEmail`,
88
+ `user-email`, `USER_EMAIL`, `email_address` all match):
89
+
90
+ - Auth credentials: `authorization`, `cookie`, `set-cookie`,
91
+ `password`, `passwd`, `secret`, `token`, `access_token`,
92
+ `refresh_token`, `session`, `session_token`, `api_key`, `apikey`,
93
+ `private_key`
94
+ - PII categories: `email`, `phone`, `credit_card`, `ssn`
95
+
96
+ This list is a strict superset of the server-side ingest hard-deny
97
+ (`@cluebase/shared` `INGEST_HARD_DENY_KEYS`), enforced by the
98
+ `tests/test_privacy_parity.py` cross-SDK parity test.
99
+
100
+ ### 2. HTTP request values
101
+
102
+ This SDK does not read HTTP request or response bodies. The value surface it can
103
+ observe is the request query, and its values are masked by default. A value is
104
+ sent in plaintext only when the customer names that field on the Cluebase screen; the
105
+ decision is delivered to the SDK and re-checked by ingest. Hard-denied keys such
106
+ as `email` can never be released, whatever the screen says. See
107
+ `docs/arch/sdk/backend-sdk.md` for the boundary and for what would be required to
108
+ bring bodies into scope.
109
+
110
+ ### 3. Analysis projection and later registration
111
+
112
+ Properties that pass the hard-deny gate are projected by default unless a
113
+ cardinality, free-text, or complex-value guard keeps them out of the analysis
114
+ index. Guarded non-private values remain available in
115
+ `analysisProperties.raw_only` so they can be registered and promoted later.
116
+ The map stores each original value as JSON so its type can be restored.
117
+ Hard-denied values are dropped and never stored in `raw_only`. After a privacy
118
+ review, use the project service allowlist to promote a guarded non-private
119
+ property into the analysis projection.
120
+
121
+ ### 4. Identity traits stay privacy-safe
122
+
123
+ `cluebase.identify(user_id, traits={...})` accepts profile traits such as
124
+ `name`, `email`, and `avatarUrl`. Other customer-defined values remain generic
125
+ subject traits instead of becoming fixed Cluebase profile fields. Use a stable opaque
126
+ `user_id`; the SDK converts `email` into privacy-safe contact-derived
127
+ fields before canonical identity events leave the process.
128
+
129
+ ## Build / test
130
+
131
+ ```sh
132
+ python3 -m venv .venv
133
+ source .venv/bin/activate
134
+ pip install -e '.[test]'
135
+ python -m pytest
136
+ ```
@@ -0,0 +1,180 @@
1
+ [project]
2
+ name = "cluebase-backend-sdk"
3
+ dynamic = ["version"]
4
+ description = "Cluebase Backend SDK (Python) — OTel-based observation-source event capture for Django, FastAPI, Celery, SQLAlchemy, LangChain, LlamaIndex, CrewAI, MCP, and AI providers."
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ # OTel SDK is required because the backend SDK emits through TracerProvider,
8
+ # BatchSpanProcessor, and ObservationSourceEventExporter.
9
+ dependencies = [
10
+ "opentelemetry-api>=1.41.1,<2.0",
11
+ "opentelemetry-sdk>=1.41.1,<2.0",
12
+ "rfc8785>=0.1.4,<0.2",
13
+ ]
14
+
15
+ [project.optional-dependencies]
16
+ # 断定の強さを測る道具(`[tool.mutmut]` が設定を持つ)。**`test` とは分けます。**
17
+ # 変異試験だけを入れて走らせられる形にしないと、設定だけが在って 1 度も動かない
18
+ # 状態になります(実測 2026-08-22: 設定は書かれているのに実行体が入っておらず、
19
+ # CI からも呼ばれていませんでした)。
20
+ mutation = ["mutmut>=3.2,<4.0"]
21
+ # Auto-instrumentation packages are loaded lazily by each
22
+ # ``instrument_*`` helper. Downstream consumers opt into the ones they need
23
+ # via these extras; the helpers degrade to a no-op when the corresponding
24
+ # instrumentation is not installed.
25
+ instrumentation-requests = ["opentelemetry-instrumentation-requests>=0.63b1,<1.0"]
26
+ instrumentation-httpx = ["opentelemetry-instrumentation-httpx>=0.63b1,<1.0"]
27
+ instrumentation-fastapi = [
28
+ "fastapi>=0.100,<1.0",
29
+ "opentelemetry-instrumentation-fastapi>=0.63b1,<1.0",
30
+ ]
31
+ instrumentation-asgi = ["opentelemetry-instrumentation-asgi>=0.63b1,<1.0"]
32
+ instrumentation-sqlalchemy = ["opentelemetry-instrumentation-sqlalchemy>=0.63b1,<1.0"]
33
+ instrumentation-django = ["opentelemetry-instrumentation-django>=0.63b1,<1.0"]
34
+ instrumentation-celery = ["opentelemetry-instrumentation-celery>=0.63b1,<1.0"]
35
+ test = [
36
+ "pytest>=8.0,<9.0",
37
+ "pytest-cov>=5.0,<7.0",
38
+ "pyright>=1.1.411,<1.2",
39
+ "build>=1.2,<2.0",
40
+ "packaging>=25,<26",
41
+ "twine>=6.1,<7.0",
42
+ "opentelemetry-instrumentation-requests>=0.63b1,<1.0",
43
+ "opentelemetry-instrumentation-httpx>=0.63b1,<1.0",
44
+ "opentelemetry-instrumentation-fastapi>=0.63b1,<1.0",
45
+ "opentelemetry-instrumentation-asgi>=0.63b1,<1.0",
46
+ "opentelemetry-instrumentation-sqlalchemy>=0.63b1,<1.0",
47
+ "opentelemetry-instrumentation-django>=0.63b1,<1.0",
48
+ "opentelemetry-instrumentation-celery>=0.63b1,<1.0",
49
+ # The `instrument_*` helpers verify peer libraries before activating
50
+ # upstream instrumentors. These runtime peers are not transitive deps of
51
+ # the OTel instrumentors, so tests install them explicitly.
52
+ "requests>=2.31",
53
+ "httpx>=0.26",
54
+ "sqlalchemy>=2.0",
55
+ "django>=5.0,<6.0",
56
+ "fastapi>=0.100,<1.0",
57
+ "celery>=5.0,<6.0",
58
+ "langchain-core>=0.3,<1.0",
59
+ "llama-index-core>=0.12,<1.0",
60
+ "crewai>=1.15.1,<1.16",
61
+ "openai==2.45.0",
62
+ "anthropic==0.116.0",
63
+ "opentelemetry-instrumentation-openai==0.62.1",
64
+ "opentelemetry-instrumentation-anthropic==0.62.1",
65
+ "aiosqlite==0.21.0",
66
+ "setuptools>=68,<83",
67
+ ]
68
+
69
+ [build-system]
70
+ requires = ["setuptools>=68"]
71
+ build-backend = "setuptools.build_meta"
72
+
73
+ [tool.setuptools]
74
+ package-dir = {"" = "src"}
75
+
76
+ [tool.setuptools.dynamic]
77
+ version = {attr = "cluebase_backend_sdk._version.__version__"}
78
+
79
+ [tool.setuptools.packages.find]
80
+ where = ["src"]
81
+
82
+ [tool.pytest.ini_options]
83
+ testpaths = [
84
+ "../../../tests/unit/backend-sdk-python",
85
+ "../../../tests/integration/backend-sdk-python",
86
+ "../../../tests/contract/backend-sdk-python",
87
+ ]
88
+ # 実環境が要る試験を既定の実行から外します (`tests/README.md` §12.2)。
89
+ # `testpaths` 配下の file には `--ignore-glob` が効かないため、live file は
90
+ # 既定実行から明示的に外します。実環境のあるジョブは path を名指しして実行します。
91
+ addopts = [
92
+ "--ignore=../../../tests/integration/backend-sdk-python/test_canonical_endpoint_live.py",
93
+ "--ignore=../../../tests/integration/backend-sdk-python/test_provider_content_privacy_live.py",
94
+ ]
95
+ # The second entry lets cross-language conformance tests import the contract's
96
+ # official fixture loader (python_loader / python_equality) in dev. This is the
97
+ # dev-test path only; the published wheel never ships tests, so it does not
98
+ # duplicate the copy-on-build of _conformance/ used by installed SDKs.
99
+ pythonpath = [
100
+ ".",
101
+ "src",
102
+ "../../../tests/support/backend-sdk-python",
103
+ "../../../tests/unit/backend-sdk-python",
104
+ "../../../tests/integration/backend-sdk-python",
105
+ "../../../tests/contract/backend-sdk-python",
106
+ ]
107
+
108
+ # tests/README.md §5 の判定器。変異させる記号は網羅の情報から自動で選ばれるので、
109
+ # 手で宣言しません。
110
+ [tool.mutmut]
111
+ # mutmut はここに挙げた木を `mutants/` へ複製してから試験を回すので、範囲を
112
+ # 1 file に絞ると import 先が複製されず collect が落ちます。木は package 全体を
113
+ # 渡し、実際に壊す範囲は `mutmut run <glob>` の側で絞ります。
114
+ source_paths = ["src/cluebase_backend_sdk/"]
115
+ # 試験が package 直下から取り込む先。複製されないと collect が落ちます
116
+ # (tests/ と pyproject.toml と uv.lock は mutmut が既定で複製します)。
117
+ also_copy = ["scripts/", "setup.py"]
118
+ # 下の 2 本を **変異の走行からだけ** 外します。素の束(`pytest`)では今までどおり
119
+ # 走ります。理由は 2 本とも同じで、**mutmut が変異を source file 自体へ書き込む**
120
+ # ためです(文字列は `XX...XX` の形、関数は `x_名前__mutmut_1` を同じ module へ
121
+ # 足す)。原文の字面と名前の面が常に変わっているので、この 2 本は **どの変異でも
122
+ # 必ず落ちます**。必ず落ちる試験があると全変異が「殺した」と記録され、**生き残りが
123
+ # 隠れます**(tests/README.md §5.5 の裏返しで、測定が実際より良く見える向き)。
124
+ #
125
+ # 2 本が見ているのは実行時の振る舞いではなく構造なので、tests/README.md 上の担当は
126
+ # §5(断定の強さ)ではなく §4(構造の不変条件)です。§5 から外すのが正しい配置で、
127
+ # 緩めではありません。
128
+ #
129
+ # 複製先で実際に落ちることを 1 本ずつ確かめた上で外しています。**同じ探し方で
130
+ # 引っかかった `tests/test_prepublish_gate.py` は、複製先でも 13 passed で通る
131
+ # ので外していません**(読んでいるのが原文の中身ではなく file の一覧で、mutmut は
132
+ # file を足さないため)。
133
+ pytest_add_cli_args_test_selection = [
134
+ "tests/",
135
+ # 原文を読む: 全 .py を ast で読み、診断の記録が読める形で出るか確かめる
136
+ "--ignore=tests/test_internal_log_messages.py",
137
+ # 名前の面を数える: 輸出された名前が契約の宣言と一致するか確かめる
138
+ "--ignore=tests/test_mcp_observer_surface.py",
139
+ ]
140
+
141
+ [tool.pyright]
142
+ # Tests resolve the package via a runtime ``sys.path.insert`` to the
143
+ # ``src/`` layout described by ``[tool.setuptools].package-dir``. Pyright
144
+ # does not execute that snippet, so we mirror the search path here so
145
+ # imports like ``from cluebase_backend_sdk.provider.bootstrap import ...``
146
+ # resolve in editors without requiring an editable install.
147
+ include = ["src", "tests"]
148
+ extraPaths = [".", "src"]
149
+
150
+ # Production code in ``src/`` is held to strict type-correctness, but
151
+ # tests freely use ``JsonValue`` union subscripts and assertX helpers
152
+ # that Pyright cannot narrow without per-call casts. Tests are gated
153
+ # by ``pytest`` at runtime, so demote Pyright's general type issues
154
+ # under ``tests/`` to warnings instead of errors.
155
+ [[tool.pyright.executionEnvironments]]
156
+ root = "tests"
157
+ reportGeneralTypeIssues = "warning"
158
+ reportOptionalSubscript = "warning"
159
+ reportOptionalMemberAccess = "warning"
160
+
161
+ [tool.coverage.run]
162
+ branch = true
163
+ source = ["cluebase_backend_sdk"]
164
+
165
+ [tool.coverage.report]
166
+ # Regression floor: keep combined statement and branch coverage high enough to
167
+ # block large untested modules without failing on small coverage noise.
168
+ fail_under = 80
169
+ precision = 2
170
+ exclude_lines = [
171
+ "pragma: no cover",
172
+ "raise NotImplementedError",
173
+ "if TYPE_CHECKING:",
174
+ # Defensive ImportError handlers for optional peer dependencies
175
+ # (sqlalchemy, django, celery, opentelemetry-instrumentation-*). The
176
+ # module degrades to a no-op when the framework is absent; that branch
177
+ # is exercised in test environments where the framework happens to be
178
+ # installed but not in unit tests.
179
+ "except ImportError",
180
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,20 @@
1
+ from __future__ import annotations
2
+
3
+ import runpy
4
+ from pathlib import Path
5
+
6
+ from setuptools import find_packages, setup
7
+
8
+
9
+ _VERSION_FILE = Path(__file__).parent / "src" / "cluebase_backend_sdk" / "_version.py"
10
+ _VERSION = runpy.run_path(str(_VERSION_FILE))["__version__"]
11
+
12
+
13
+ setup(
14
+ name="cluebase-backend-sdk",
15
+ version=_VERSION,
16
+ description="Cluebase Backend SDK (Python) — OTel-based observation source event capture",
17
+ python_requires=">=3.10",
18
+ package_dir={"": "src"},
19
+ packages=find_packages("src"),
20
+ )