scope-analytics 0.1.3__tar.gz → 0.1.4__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 (42) hide show
  1. {scope_analytics-0.1.3/scope_analytics.egg-info → scope_analytics-0.1.4}/PKG-INFO +1 -1
  2. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/events.py +7 -4
  3. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/supported_versions.py +12 -9
  4. {scope_analytics-0.1.3 → scope_analytics-0.1.4/scope_analytics.egg-info}/PKG-INFO +1 -1
  5. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics.egg-info/SOURCES.txt +2 -1
  6. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/setup.py +1 -1
  7. scope_analytics-0.1.4/tests/test_user_facing.py +63 -0
  8. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/LICENSE +0 -0
  9. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/MANIFEST.in +0 -0
  10. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/README.md +0 -0
  11. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/__init__.py +0 -0
  12. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/auto.py +0 -0
  13. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/cli.py +0 -0
  14. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/client.py +0 -0
  15. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/config.py +0 -0
  16. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/context.py +0 -0
  17. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/deployment.py +0 -0
  18. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/middleware.py +0 -0
  19. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/__init__.py +0 -0
  20. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/_capture.py +0 -0
  21. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/_streaming.py +0 -0
  22. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/anthropic_patch.py +0 -0
  23. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/gemini_patch.py +0 -0
  24. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/google_genai_patch.py +0 -0
  25. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/http_patch.py +0 -0
  26. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/openai_patch.py +0 -0
  27. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/queue.py +0 -0
  28. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics.egg-info/dependency_links.txt +0 -0
  29. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics.egg-info/entry_points.txt +0 -0
  30. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics.egg-info/requires.txt +0 -0
  31. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics.egg-info/top_level.txt +0 -0
  32. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/setup.cfg +0 -0
  33. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_capture_contract.py +0 -0
  34. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_capture_contract_google.py +0 -0
  35. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_cli_exec.py +0 -0
  36. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_coverage_honesty.py +0 -0
  37. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_deployment.py +0 -0
  38. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_http_patch.py +0 -0
  39. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_identify.py +0 -0
  40. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_identity_bridge.py +0 -0
  41. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_patch_versions.py +0 -0
  42. {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_redaction.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scope-analytics
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: AI-powered analytics SDK for backend applications with automatic LLM tracking
5
5
  Home-page: https://scopeai.dev
6
6
  Author: Scope AI
@@ -54,8 +54,10 @@ class EventFormatter:
54
54
  user_agent = ScopeContext.get_user_agent()
55
55
 
56
56
  # Determine if this is a user-facing LLM call
57
- # If we have a session_id from frontend (not temp_), it's user-facing
58
- is_user_facing = session_id and not session_id.startswith("temp_")
57
+ # If we have a session_id from frontend (not temp_), it's user-facing. A real boolean
58
+ # either way: before 0.1.4 a call with no session at all shipped None here (the bare
59
+ # `and` chain), so the first context-less call of a task was neither true nor false.
60
+ is_user_facing = bool(session_id and not session_id.startswith("temp_"))
59
61
 
60
62
  # Messages may carry SDK objects (pydantic content blocks appended by the app);
61
63
  # coerce to JSON-safe types FIRST so prompt extraction sees plain dicts and the
@@ -159,8 +161,9 @@ class EventFormatter:
159
161
  user_id = ScopeContext.get_user_id()
160
162
 
161
163
  # Determine if this is a user-facing request
162
- # If we have a session_id from frontend (not temp_), it's user-initiated
163
- is_user_facing = session_id and not session_id.startswith("temp_")
164
+ # If we have a session_id from frontend (not temp_), it's user-initiated. A real
165
+ # boolean either way (see format_llm_call).
166
+ is_user_facing = bool(session_id and not session_id.startswith("temp_"))
164
167
 
165
168
  event = {
166
169
  "event_type": "http_request",
@@ -34,9 +34,10 @@ SUPPORTED_VERSIONS = {
34
34
  # stream contract itself, failing the contract cells with Scope OFF); contract-grade
35
35
  # verification starts at 1.30.0 (pinned cell, httpx<0.28 for its proxies= era).
36
36
  # 3.x (2026-09): the majors keep the modern client shape, so the structural routing and
37
- # both capture surfaces are unchanged — verified by the contract cells below.
38
- "max_tested": "3.8.0",
39
- "ci_matrix": ["1.0.0", "1.30.0", "1.57.0", "2.41.0", "2.46.0", "3.8.0"],
37
+ # both capture surfaces are unchanged — verified by the contract cells below (3.8.0 at
38
+ # 0.1.3; 3.14.1 at 0.1.4, the publish gate's floating "latest" cell + a clean venv).
39
+ "max_tested": "3.14.1",
40
+ "ci_matrix": ["1.0.0", "1.30.0", "1.57.0", "2.41.0", "2.46.0", "3.8.0", "3.14.1"],
40
41
  },
41
42
  "anthropic": {
42
43
  "import_name": "anthropic",
@@ -47,9 +48,10 @@ SUPPORTED_VERSIONS = {
47
48
  # SDKs that predate those surfaces.
48
49
  # 1.x (2026-09) moved the SDK's HTTP client from httpx to httpx2 — which is why outbound
49
50
  # capture patches httpx2 as its own target, and why the structural cell reads the http
50
- # module off the SDK instead of assuming ours. The patch surfaces themselves are unchanged.
51
- "max_tested": "1.4.0",
52
- "ci_matrix": ["0.18.0", "0.30.0", "0.40.0", "0.116.0", "1.4.0"],
51
+ # module off the SDK instead of assuming ours. The patch surfaces themselves are unchanged
52
+ # (1.4.0 verified at 0.1.3; 1.6.0 at 0.1.4).
53
+ "max_tested": "1.6.0",
54
+ "ci_matrix": ["0.18.0", "0.30.0", "0.40.0", "0.116.0", "1.4.0", "1.6.0"],
53
55
  },
54
56
  "gemini": {
55
57
  # Targets the LEGACY google-generativeai SDK (import google.generativeai), which gemini_patch.py
@@ -76,9 +78,10 @@ SUPPORTED_VERSIONS = {
76
78
  "dist_name": "google-genai",
77
79
  "min_supported": "1.0.0", # the unified SDK GA'd at v1.x (~May 2025)
78
80
  # Verified by tests/test_capture_contract_google.py against 1.60.0 AND 2.11.0 (current as of
79
- # 2026-07; the 2.0 major's breaking changes were interactions-only — generate_content stable).
80
- "max_tested": "2.22.0",
81
- "ci_matrix": ["1.0.0", "1.60.0", "2.11.0", "2.22.0"],
81
+ # 2026-07; the 2.0 major's breaking changes were interactions-only — generate_content stable);
82
+ # 2.22.0 at 0.1.3, 2.23.0 at 0.1.4.
83
+ "max_tested": "2.23.0",
84
+ "ci_matrix": ["1.0.0", "1.60.0", "2.11.0", "2.22.0", "2.23.0"],
82
85
  },
83
86
  }
84
87
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scope-analytics
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: AI-powered analytics SDK for backend applications with automatic LLM tracking
5
5
  Home-page: https://scopeai.dev
6
6
  Author: Scope AI
@@ -36,4 +36,5 @@ tests/test_http_patch.py
36
36
  tests/test_identify.py
37
37
  tests/test_identity_bridge.py
38
38
  tests/test_patch_versions.py
39
- tests/test_redaction.py
39
+ tests/test_redaction.py
40
+ tests/test_user_facing.py
@@ -11,7 +11,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
11
11
 
12
12
  setup(
13
13
  name="scope-analytics",
14
- version="0.1.3",
14
+ version="0.1.4",
15
15
  author="Scope AI",
16
16
  author_email="support@scopeai.dev",
17
17
  description="AI-powered analytics SDK for backend applications with automatic LLM tracking",
@@ -0,0 +1,63 @@
1
+ """`is_user_facing` is a real boolean on every event the formatter writes.
2
+
3
+ Before 0.1.4 `format_llm_call` and `format_http_request` computed it as the bare chain
4
+ `session_id and not session_id.startswith("temp_")`, which is None when no session is in
5
+ the context at all — so the FIRST context-less call of a background task shipped
6
+ `is_user_facing: null`, neither true nor false, while every later call on that thread (the
7
+ temp session now pinned) shipped false. A filter on `false` missed those first calls; the
8
+ analyst's schema had to teach `{$ne: true}` around it. `format_external_api_call` had the
9
+ bool() from the start; this pins all three to the same truth table.
10
+ """
11
+ import pytest
12
+
13
+ from scope_analytics.config import ScopeConfig
14
+ from scope_analytics.context import ScopeContext
15
+ from scope_analytics.events import EventFormatter
16
+
17
+
18
+ @pytest.fixture
19
+ def formatter():
20
+ ScopeContext.clear()
21
+ yield EventFormatter(ScopeConfig(api_key="sk_test_user_facing"))
22
+ ScopeContext.clear()
23
+
24
+
25
+ def _llm(f):
26
+ return f.format_llm_call("openai", "gpt", [{"role": "user", "content": "hi"}], "yo")
27
+
28
+
29
+ def _http(f):
30
+ return f.format_http_request("GET", "/x", 200, 1.0)
31
+
32
+
33
+ def _outbound(f):
34
+ return f.format_external_api_call("GET", "https://api.example.com/v1/x", 200, 1.0)
35
+
36
+
37
+ @pytest.mark.parametrize("build", [_llm, _http, _outbound], ids=["llm_call", "http_request", "external_api_call"])
38
+ def test_no_session_at_all_is_false_not_none(formatter, build):
39
+ """The first context-less call — nothing pinned yet — is `false`, the same as every call after it."""
40
+ assert ScopeContext.get_session_id() is None
41
+ event = build(formatter)
42
+ assert event["is_user_facing"] is False, event["is_user_facing"]
43
+
44
+
45
+ @pytest.mark.parametrize("build", [_llm, _http, _outbound], ids=["llm_call", "http_request", "external_api_call"])
46
+ def test_a_temp_session_is_false(formatter, build):
47
+ ScopeContext.set_session_id("temp_0123456789abcdef")
48
+ assert build(formatter)["is_user_facing"] is False
49
+
50
+
51
+ @pytest.mark.parametrize("build", [_llm, _http, _outbound], ids=["llm_call", "http_request", "external_api_call"])
52
+ def test_a_visitor_session_is_true(formatter, build):
53
+ ScopeContext.set_session_id("sess_from_the_browser")
54
+ assert build(formatter)["is_user_facing"] is True
55
+
56
+
57
+ def test_the_first_and_second_context_less_calls_agree(formatter):
58
+ """The regression's exact shape: call one had no session, call two saw the temp session
59
+ call one pinned — they must say the same thing."""
60
+ first = _llm(formatter)["is_user_facing"]
61
+ assert ScopeContext.get_session_id().startswith("temp_")
62
+ second = _llm(formatter)["is_user_facing"]
63
+ assert first is False and second is False
File without changes