posthoganalytics 6.3.1__py3-none-any.whl → 6.3.3__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.
@@ -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 choice.text:
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, "tools") and response.tools and len(response.tools) > 0:
229
- return response.tools
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
+ )
posthoganalytics/types.py CHANGED
@@ -110,7 +110,7 @@ class FeatureFlag:
110
110
  variant=variant,
111
111
  reason=None,
112
112
  metadata=LegacyFlagMetadata(
113
- payload=payload if payload else None,
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) if isinstance(payload, str) else 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) and value.enabled and value.metadata.payload
302
+ if isinstance(value, FeatureFlag)
303
+ and value.enabled
304
+ and value.metadata.payload is not None
300
305
  }
@@ -1,4 +1,4 @@
1
- VERSION = "6.3.1"
1
+ VERSION = "6.3.3"
2
2
 
3
3
  if __name__ == "__main__":
4
4
  print(VERSION, end="") # noqa: T201
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: posthoganalytics
3
- Version: 6.3.1
3
+ Version: 6.3.3
4
4
  Summary: Integrate PostHog into any python application.
5
5
  Home-page: https://github.com/posthog/posthog-python
6
6
  Author: Posthog
@@ -1,6 +1,6 @@
1
1
  posthoganalytics/__init__.py,sha256=N_Mq_RDLFbd02HCMWxJPTtcSYj3NWcartohQKa7MNlw,24133
2
2
  posthoganalytics/args.py,sha256=iZ2JWeANiAREJKhS-Qls9tIngjJOSfAVR8C4xFT5sHw,3307
3
- posthoganalytics/client.py,sha256=y7bUrKYaCGPWnAkOfYrLEVFD7Buoc4dZor6yKB51O_E,67611
3
+ posthoganalytics/client.py,sha256=P3fBadb30Gohz41yQAfzqKBo4a78h4IjNb3EpX041tM,67647
4
4
  posthoganalytics/consumer.py,sha256=CiNbJBdyW9jER3ZYCKbX-JFmEDXlE1lbDy1MSl43-a0,4617
5
5
  posthoganalytics/contexts.py,sha256=LFSFIYpUFWKTBnGMjV9n1aYHWbAzz5zLJGr2qG34PoE,9405
6
6
  posthoganalytics/exception_capture.py,sha256=1VHBfffrXXrkK0PT8iVgKPpj_R1pGAzG5f3Qw0WF79w,1783
@@ -9,11 +9,11 @@ posthoganalytics/feature_flags.py,sha256=O_kXmw3goB2E9XMBosdPeBAuo9MsnsH8PyNWq95
9
9
  posthoganalytics/poller.py,sha256=jBz5rfH_kn_bBz7wCB46Fpvso4ttx4uzqIZWvXBCFmQ,595
10
10
  posthoganalytics/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  posthoganalytics/request.py,sha256=TaeySYpcvHMf5Ftf5KqqlO0VPJpirKBCRrThlS04Kew,6124
12
- posthoganalytics/types.py,sha256=2rwhiZd9lvs37MiXEBADVdMKvcCvFXfAMgIUJ8KNTBs,10005
12
+ posthoganalytics/types.py,sha256=k_IE_tvAE7wBKHthTSPEf4zB-SPuK2y3LDlsGXuU5_8,10093
13
13
  posthoganalytics/utils.py,sha256=-0w-OLcCaoldkbBebPzQyBzLJSo9G9yBOg8NDVz7La8,16088
14
- posthoganalytics/version.py,sha256=QZXrqI6X21wlR2zfls818OHusGGPo_YZf4UfdcGv4ds,87
14
+ posthoganalytics/version.py,sha256=TwaNyeWFPCGS2dLL3HiS9owbDI9LX2LeDWSL03vZecg,87
15
15
  posthoganalytics/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- posthoganalytics/ai/utils.py,sha256=5-2XfmetCs0v9otBoux7-IEG933wAnKLSGS6oYLqCkw,19529
16
+ posthoganalytics/ai/utils.py,sha256=-iT0gOf_5Q3E6X20jKGOyJgyarwkml73vaf60bUzRtM,20165
17
17
  posthoganalytics/ai/anthropic/__init__.py,sha256=fFhDOiRzTXzGQlgnrRDL-4yKC8EYIl8NW4a2QNR6xRU,368
18
18
  posthoganalytics/ai/anthropic/anthropic.py,sha256=P8o-pZ2rbJXDiHO73OWjO7OgboGiEm_wVY4pbvHnUEs,7397
19
19
  posthoganalytics/ai/anthropic/anthropic_async.py,sha256=iAwVlAY6VeW0dGZdMkdfniBTBFUdZZrDMZi-O9vdiuo,7511
@@ -30,7 +30,7 @@ posthoganalytics/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5N
30
30
  posthoganalytics/integrations/django.py,sha256=KYtBr7CkiZQynRc2TCWWYHe-J3ie8iSUa42WPshYZdc,6795
31
31
  posthoganalytics/test/__init__.py,sha256=VYgM6xPbJbvS-xhIcDiBRs0MFC9V_jT65uNeerCz_rM,299
32
32
  posthoganalytics/test/test_before_send.py,sha256=A1_UVMewhHAvO39rZDWfS606vG_X-q0KNXvh5DAKiB8,7930
33
- posthoganalytics/test/test_client.py,sha256=RJURSog8fX_YEBzv4MGrX1amX0Ma7NUMlwSanfgORmQ,87343
33
+ posthoganalytics/test/test_client.py,sha256=PDsEgJ5nFZh75bJ8Vugyc_C5iRtGOU8qWYPYDF8z24c,91606
34
34
  posthoganalytics/test/test_consumer.py,sha256=HGMfU9PzQ5ZAe_R3kHnZNsMvD7jUjHL-gie0isrvMMk,7107
35
35
  posthoganalytics/test/test_contexts.py,sha256=c--hNUIEf6SHQ7H9vdPhU1oLCN0SnD4wDbFr-eLPHDo,7013
36
36
  posthoganalytics/test/test_exception_capture.py,sha256=al37Kg6wjzL_IBCFUUXRvkP6nVrqS6IZRCOKSo29Nh8,1063
@@ -42,8 +42,8 @@ posthoganalytics/test/test_request.py,sha256=Zc0VbkjpVmj8mKokQm9rzdgTr0b1U44vvMY
42
42
  posthoganalytics/test/test_size_limited_dict.py,sha256=-5IQjIEr_-Dql24M0HusdR_XroOMrtgiT0v6ZQCRvzo,774
43
43
  posthoganalytics/test/test_types.py,sha256=bRPHdwVpP7hu7emsplU8UVyzSQptv6PaG5lAoOD_BtM,7595
44
44
  posthoganalytics/test/test_utils.py,sha256=sqUTbfweVcxxFRd3WDMFXqPMyU6DvzOBeAOc68Py9aw,9620
45
- posthoganalytics-6.3.1.dist-info/licenses/LICENSE,sha256=wGf9JBotDkSygFj43m49oiKlFnpMnn97keiZKF-40vE,2450
46
- posthoganalytics-6.3.1.dist-info/METADATA,sha256=rfwZmeIdtNRF30u2_F5RSi_SCm4ttMQu5xzs34JoN8c,6024
47
- posthoganalytics-6.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
48
- posthoganalytics-6.3.1.dist-info/top_level.txt,sha256=8QsNIqIkBh1p2TXvKp0Em9ZLZKwe3uIqCETyW4s1GOE,17
49
- posthoganalytics-6.3.1.dist-info/RECORD,,
45
+ posthoganalytics-6.3.3.dist-info/licenses/LICENSE,sha256=wGf9JBotDkSygFj43m49oiKlFnpMnn97keiZKF-40vE,2450
46
+ posthoganalytics-6.3.3.dist-info/METADATA,sha256=N6EyQGMGCozK-Hb73B5dTMLF_m7G-71zb0wiY3E5ytM,6024
47
+ posthoganalytics-6.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
48
+ posthoganalytics-6.3.3.dist-info/top_level.txt,sha256=8QsNIqIkBh1p2TXvKp0Em9ZLZKwe3uIqCETyW4s1GOE,17
49
+ posthoganalytics-6.3.3.dist-info/RECORD,,