posthoganalytics 6.3.1__tar.gz → 6.3.3__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.
- {posthoganalytics-6.3.1/posthoganalytics.egg-info → posthoganalytics-6.3.3}/PKG-INFO +1 -1
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/utils.py +22 -3
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/client.py +3 -3
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_client.py +109 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/types.py +8 -3
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/version.py +1 -1
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3/posthoganalytics.egg-info}/PKG-INFO +1 -1
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/LICENSE +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/MANIFEST.in +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/README.md +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/__init__.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/__init__.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/anthropic/__init__.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/anthropic/anthropic.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/anthropic/anthropic_async.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/anthropic/anthropic_providers.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/gemini/__init__.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/gemini/gemini.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/langchain/__init__.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/langchain/callbacks.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/openai/__init__.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/openai/openai.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/openai/openai_async.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/openai/openai_providers.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/args.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/consumer.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/contexts.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/exception_capture.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/exception_utils.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/feature_flags.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/integrations/__init__.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/integrations/django.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/poller.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/py.typed +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/request.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/__init__.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_before_send.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_consumer.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_contexts.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_exception_capture.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_feature_flag.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_feature_flag_result.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_feature_flags.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_module.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_request.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_size_limited_dict.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_types.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_utils.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/utils.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics.egg-info/SOURCES.txt +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics.egg-info/dependency_links.txt +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics.egg-info/requires.txt +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics.egg-info/top_level.txt +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/pyproject.toml +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/setup.cfg +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/setup.py +0 -0
- {posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/setup_analytics.py +0 -0
|
@@ -118,7 +118,12 @@ def format_response(response, provider: str):
|
|
|
118
118
|
def format_response_anthropic(response):
|
|
119
119
|
output = []
|
|
120
120
|
for choice in response.content:
|
|
121
|
-
if
|
|
121
|
+
if (
|
|
122
|
+
hasattr(choice, "type")
|
|
123
|
+
and choice.type == "text"
|
|
124
|
+
and hasattr(choice, "text")
|
|
125
|
+
and choice.text
|
|
126
|
+
):
|
|
122
127
|
output.append(
|
|
123
128
|
{
|
|
124
129
|
"role": "assistant",
|
|
@@ -225,8 +230,21 @@ def format_response_gemini(response):
|
|
|
225
230
|
|
|
226
231
|
def format_tool_calls(response, provider: str):
|
|
227
232
|
if provider == "anthropic":
|
|
228
|
-
if hasattr(response, "
|
|
229
|
-
|
|
233
|
+
if hasattr(response, "content") and response.content:
|
|
234
|
+
tool_calls = []
|
|
235
|
+
|
|
236
|
+
for content_item in response.content:
|
|
237
|
+
if hasattr(content_item, "type") and content_item.type == "tool_use":
|
|
238
|
+
tool_calls.append(
|
|
239
|
+
{
|
|
240
|
+
"type": content_item.type,
|
|
241
|
+
"id": content_item.id,
|
|
242
|
+
"name": content_item.name,
|
|
243
|
+
"input": content_item.input,
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
return tool_calls if tool_calls else None
|
|
230
248
|
elif provider == "openai":
|
|
231
249
|
# Handle both Chat Completions and Responses API
|
|
232
250
|
if hasattr(response, "choices") and response.choices:
|
|
@@ -378,6 +396,7 @@ def call_llm_and_track_usage(
|
|
|
378
396
|
}
|
|
379
397
|
|
|
380
398
|
tool_calls = format_tool_calls(response, provider)
|
|
399
|
+
|
|
381
400
|
if tool_calls:
|
|
382
401
|
event_properties["$ai_tools"] = with_privacy_mode(
|
|
383
402
|
ph_client, posthog_privacy_mode, tool_calls
|
|
@@ -1285,7 +1285,7 @@ class Client(object):
|
|
|
1285
1285
|
lookup_match_value = override_match_value or flag_value
|
|
1286
1286
|
payload = (
|
|
1287
1287
|
self._compute_payload_locally(key, lookup_match_value)
|
|
1288
|
-
if lookup_match_value
|
|
1288
|
+
if lookup_match_value is not None
|
|
1289
1289
|
else None
|
|
1290
1290
|
)
|
|
1291
1291
|
flag_result = FeatureFlagResult.from_value_and_payload(
|
|
@@ -1586,7 +1586,7 @@ class Client(object):
|
|
|
1586
1586
|
f"$feature/{key}": response,
|
|
1587
1587
|
}
|
|
1588
1588
|
|
|
1589
|
-
if payload:
|
|
1589
|
+
if payload is not None:
|
|
1590
1590
|
# if payload is not a string, json serialize it to a string
|
|
1591
1591
|
properties["$feature_flag_payload"] = payload
|
|
1592
1592
|
|
|
@@ -1790,7 +1790,7 @@ class Client(object):
|
|
|
1790
1790
|
matched_payload = self._compute_payload_locally(
|
|
1791
1791
|
flag["key"], flags[flag["key"]]
|
|
1792
1792
|
)
|
|
1793
|
-
if matched_payload:
|
|
1793
|
+
if matched_payload is not None:
|
|
1794
1794
|
payloads[flag["key"]] = matched_payload
|
|
1795
1795
|
except InconclusiveMatchError:
|
|
1796
1796
|
# No need to log this, since it's just telling us to fall back to `/decide`
|
|
@@ -2246,3 +2246,112 @@ class TestClient(unittest.TestCase):
|
|
|
2246
2246
|
with self.assertRaises(TypeError) as cm:
|
|
2247
2247
|
client._parse_send_feature_flags(None)
|
|
2248
2248
|
self.assertIn("Invalid type for send_feature_flags", str(cm.exception))
|
|
2249
|
+
|
|
2250
|
+
@mock.patch("posthog.client.batch_post")
|
|
2251
|
+
def test_get_feature_flag_result_with_empty_string_payload(self, patch_batch_post):
|
|
2252
|
+
"""Test that get_feature_flag_result returns a FeatureFlagResult when payload is empty string"""
|
|
2253
|
+
client = Client(
|
|
2254
|
+
FAKE_TEST_API_KEY,
|
|
2255
|
+
personal_api_key="test_personal_api_key",
|
|
2256
|
+
sync_mode=True,
|
|
2257
|
+
)
|
|
2258
|
+
|
|
2259
|
+
# Set up local evaluation with a flag that has empty string payload
|
|
2260
|
+
client.feature_flags = [
|
|
2261
|
+
{
|
|
2262
|
+
"id": 1,
|
|
2263
|
+
"name": "Test flag",
|
|
2264
|
+
"key": "test-flag",
|
|
2265
|
+
"is_simple_flag": False,
|
|
2266
|
+
"active": True,
|
|
2267
|
+
"rollout_percentage": None,
|
|
2268
|
+
"filters": {
|
|
2269
|
+
"groups": [
|
|
2270
|
+
{
|
|
2271
|
+
"properties": [],
|
|
2272
|
+
"rollout_percentage": None,
|
|
2273
|
+
"variant": "empty-variant",
|
|
2274
|
+
}
|
|
2275
|
+
],
|
|
2276
|
+
"multivariate": {
|
|
2277
|
+
"variants": [
|
|
2278
|
+
{
|
|
2279
|
+
"key": "empty-variant",
|
|
2280
|
+
"name": "Empty Variant",
|
|
2281
|
+
"rollout_percentage": 100,
|
|
2282
|
+
}
|
|
2283
|
+
]
|
|
2284
|
+
},
|
|
2285
|
+
"payloads": {
|
|
2286
|
+
"empty-variant": "" # Empty string payload
|
|
2287
|
+
},
|
|
2288
|
+
},
|
|
2289
|
+
}
|
|
2290
|
+
]
|
|
2291
|
+
|
|
2292
|
+
# Test get_feature_flag_result
|
|
2293
|
+
result = client.get_feature_flag_result(
|
|
2294
|
+
"test-flag", "test-user", only_evaluate_locally=True
|
|
2295
|
+
)
|
|
2296
|
+
|
|
2297
|
+
# Should return a FeatureFlagResult, not None
|
|
2298
|
+
self.assertIsNotNone(result)
|
|
2299
|
+
self.assertEqual(result.key, "test-flag")
|
|
2300
|
+
self.assertEqual(result.get_value(), "empty-variant")
|
|
2301
|
+
self.assertEqual(result.payload, "") # Should be empty string, not None
|
|
2302
|
+
|
|
2303
|
+
@mock.patch("posthog.client.batch_post")
|
|
2304
|
+
def test_get_all_flags_and_payloads_with_empty_string(self, patch_batch_post):
|
|
2305
|
+
"""Test that get_all_flags_and_payloads includes flags with empty string payloads"""
|
|
2306
|
+
client = Client(
|
|
2307
|
+
FAKE_TEST_API_KEY,
|
|
2308
|
+
personal_api_key="test_personal_api_key",
|
|
2309
|
+
sync_mode=True,
|
|
2310
|
+
)
|
|
2311
|
+
|
|
2312
|
+
# Set up multiple flags with different payload types
|
|
2313
|
+
client.feature_flags = [
|
|
2314
|
+
{
|
|
2315
|
+
"id": 1,
|
|
2316
|
+
"name": "Flag with empty payload",
|
|
2317
|
+
"key": "empty-payload-flag",
|
|
2318
|
+
"is_simple_flag": False,
|
|
2319
|
+
"active": True,
|
|
2320
|
+
"filters": {
|
|
2321
|
+
"groups": [{"properties": [], "variant": "variant1"}],
|
|
2322
|
+
"multivariate": {
|
|
2323
|
+
"variants": [{"key": "variant1", "rollout_percentage": 100}]
|
|
2324
|
+
},
|
|
2325
|
+
"payloads": {"variant1": ""}, # Empty string
|
|
2326
|
+
},
|
|
2327
|
+
},
|
|
2328
|
+
{
|
|
2329
|
+
"id": 2,
|
|
2330
|
+
"name": "Flag with normal payload",
|
|
2331
|
+
"key": "normal-payload-flag",
|
|
2332
|
+
"is_simple_flag": False,
|
|
2333
|
+
"active": True,
|
|
2334
|
+
"filters": {
|
|
2335
|
+
"groups": [{"properties": [], "variant": "variant2"}],
|
|
2336
|
+
"multivariate": {
|
|
2337
|
+
"variants": [{"key": "variant2", "rollout_percentage": 100}]
|
|
2338
|
+
},
|
|
2339
|
+
"payloads": {"variant2": "normal payload"},
|
|
2340
|
+
},
|
|
2341
|
+
},
|
|
2342
|
+
]
|
|
2343
|
+
|
|
2344
|
+
result = client.get_all_flags_and_payloads(
|
|
2345
|
+
"test-user", only_evaluate_locally=True
|
|
2346
|
+
)
|
|
2347
|
+
|
|
2348
|
+
# Check that both flags are included
|
|
2349
|
+
self.assertEqual(result["featureFlags"]["empty-payload-flag"], "variant1")
|
|
2350
|
+
self.assertEqual(result["featureFlags"]["normal-payload-flag"], "variant2")
|
|
2351
|
+
|
|
2352
|
+
# Check that empty string payload is included (not filtered out)
|
|
2353
|
+
self.assertIn("empty-payload-flag", result["featureFlagPayloads"])
|
|
2354
|
+
self.assertEqual(result["featureFlagPayloads"]["empty-payload-flag"], "")
|
|
2355
|
+
self.assertEqual(
|
|
2356
|
+
result["featureFlagPayloads"]["normal-payload-flag"], "normal payload"
|
|
2357
|
+
)
|
|
@@ -110,7 +110,7 @@ class FeatureFlag:
|
|
|
110
110
|
variant=variant,
|
|
111
111
|
reason=None,
|
|
112
112
|
metadata=LegacyFlagMetadata(
|
|
113
|
-
payload=payload
|
|
113
|
+
payload=payload,
|
|
114
114
|
),
|
|
115
115
|
)
|
|
116
116
|
|
|
@@ -178,7 +178,9 @@ class FeatureFlagResult:
|
|
|
178
178
|
key=key,
|
|
179
179
|
enabled=enabled,
|
|
180
180
|
variant=variant,
|
|
181
|
-
payload=json.loads(payload)
|
|
181
|
+
payload=json.loads(payload)
|
|
182
|
+
if isinstance(payload, str) and payload
|
|
183
|
+
else payload,
|
|
182
184
|
reason=None,
|
|
183
185
|
)
|
|
184
186
|
|
|
@@ -219,6 +221,7 @@ class FeatureFlagResult:
|
|
|
219
221
|
payload=(
|
|
220
222
|
json.loads(details.metadata.payload)
|
|
221
223
|
if isinstance(details.metadata.payload, str)
|
|
224
|
+
and details.metadata.payload
|
|
222
225
|
else details.metadata.payload
|
|
223
226
|
),
|
|
224
227
|
reason=details.reason.description if details.reason else None,
|
|
@@ -296,5 +299,7 @@ def to_payloads(response: FlagsResponse) -> Optional[dict[str, str]]:
|
|
|
296
299
|
return {
|
|
297
300
|
key: value.metadata.payload
|
|
298
301
|
for key, value in response.get("flags", {}).items()
|
|
299
|
-
if isinstance(value, FeatureFlag)
|
|
302
|
+
if isinstance(value, FeatureFlag)
|
|
303
|
+
and value.enabled
|
|
304
|
+
and value.metadata.payload is not None
|
|
300
305
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/anthropic/anthropic.py
RENAMED
|
File without changes
|
{posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/anthropic/anthropic_async.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/langchain/callbacks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/openai/openai_async.py
RENAMED
|
File without changes
|
{posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/ai/openai/openai_providers.py
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
|
|
File without changes
|
{posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_exception_capture.py
RENAMED
|
File without changes
|
{posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_feature_flag.py
RENAMED
|
File without changes
|
{posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_feature_flag_result.py
RENAMED
|
File without changes
|
{posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_feature_flags.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics/test/test_size_limited_dict.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{posthoganalytics-6.3.1 → posthoganalytics-6.3.3}/posthoganalytics.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
|