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.
- {scope_analytics-0.1.3/scope_analytics.egg-info → scope_analytics-0.1.4}/PKG-INFO +1 -1
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/events.py +7 -4
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/supported_versions.py +12 -9
- {scope_analytics-0.1.3 → scope_analytics-0.1.4/scope_analytics.egg-info}/PKG-INFO +1 -1
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics.egg-info/SOURCES.txt +2 -1
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/setup.py +1 -1
- scope_analytics-0.1.4/tests/test_user_facing.py +63 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/LICENSE +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/MANIFEST.in +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/README.md +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/__init__.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/auto.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/cli.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/client.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/config.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/context.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/deployment.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/middleware.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/__init__.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/_capture.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/_streaming.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/anthropic_patch.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/gemini_patch.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/google_genai_patch.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/http_patch.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/openai_patch.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/queue.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics.egg-info/dependency_links.txt +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics.egg-info/entry_points.txt +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics.egg-info/requires.txt +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics.egg-info/top_level.txt +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/setup.cfg +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_capture_contract.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_capture_contract_google.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_cli_exec.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_coverage_honesty.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_deployment.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_http_patch.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_identify.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_identity_bridge.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_patch_versions.py +0 -0
- {scope_analytics-0.1.3 → scope_analytics-0.1.4}/tests/test_redaction.py +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
39
|
-
"
|
|
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
|
-
|
|
52
|
-
"
|
|
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
|
-
|
|
81
|
-
"
|
|
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
|
|
|
@@ -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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics/patches/google_genai_patch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{scope_analytics-0.1.3 → scope_analytics-0.1.4}/scope_analytics.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|