Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-posthog for openSUSE:Factory checked in at 2026-03-04 21:05:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-posthog (Old) and /work/SRC/openSUSE:Factory/.python-posthog.new.561 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-posthog" Wed Mar 4 21:05:32 2026 rev:5 rq:1336189 version:7.9.6 Changes: -------- --- /work/SRC/openSUSE:Factory/python-posthog/python-posthog.changes 2026-02-20 17:52:34.544413931 +0100 +++ /work/SRC/openSUSE:Factory/.python-posthog.new.561/python-posthog.changes 2026-03-04 21:05:35.093343830 +0100 @@ -1,0 +2,12 @@ +Tue Mar 3 19:08:13 UTC 2026 - Dirk Müller <[email protected]> + +- update to 7.9.6: + * refactor: add PROPERTY_OPERATORS constant for match_property + * feat: add semver targeting support to local flag evaluation + * fix: use $ip not $ip_addess + * feat(llma): add $ai_tokens_source property to detect token + value overrides + * fix: revert manual release and add sampo changeset for + ai_tokens_source + +------------------------------------------------------------------- Old: ---- posthog-7.9.3.tar.gz New: ---- posthog-7.9.6.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-posthog.spec ++++++ --- /var/tmp/diff_new_pack.5xi1KU/_old 2026-03-04 21:05:37.245432750 +0100 +++ /var/tmp/diff_new_pack.5xi1KU/_new 2026-03-04 21:05:37.261433411 +0100 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-posthog -Version: 7.9.3 +Version: 7.9.6 Release: 0 Summary: PostHog is developer-friendly, self-hosted product analytics License: MIT ++++++ posthog-7.9.3.tar.gz -> posthog-7.9.6.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/posthog-7.9.3/PKG-INFO new/posthog-7.9.6/PKG-INFO --- old/posthog-7.9.3/PKG-INFO 2026-02-18 23:20:11.462619500 +0100 +++ new/posthog-7.9.6/PKG-INFO 2026-03-02 22:28:48.419109000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: posthog -Version: 7.9.3 +Version: 7.9.6 Summary: Integrate PostHog into any python application. Home-page: https://github.com/posthog/posthog-python Author: Posthog diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/posthog-7.9.3/posthog/ai/utils.py new/posthog-7.9.6/posthog/ai/utils.py --- old/posthog-7.9.3/posthog/ai/utils.py 2026-02-18 23:18:08.000000000 +0100 +++ new/posthog-7.9.6/posthog/ai/utils.py 2026-03-02 22:28:23.000000000 +0100 @@ -13,6 +13,28 @@ from posthog.client import Client as PostHogClient +_TOKEN_PROPERTY_KEYS = frozenset( + { + "$ai_input_tokens", + "$ai_output_tokens", + "$ai_cache_read_input_tokens", + "$ai_cache_creation_input_tokens", + "$ai_total_tokens", + "$ai_reasoning_tokens", + } +) + + +def _get_tokens_source( + sdk_tags: Dict[str, Any], posthog_properties: Optional[Dict[str, Any]] +) -> str: + if posthog_properties and any( + key in posthog_properties for key in _TOKEN_PROPERTY_KEYS + ): + return "passthrough" + return "sdk" + + def serialize_raw_usage(raw_usage: Any) -> Optional[Dict[str, Any]]: """ Convert raw provider usage objects to JSON-serializable dicts. @@ -413,14 +435,19 @@ # send the event to posthog if hasattr(ph_client, "capture") and callable(ph_client.capture): + sdk_tags = get_tags() + merged_properties = { + **sdk_tags, + **(posthog_properties or {}), + **(error_params or {}), + } + merged_properties["$ai_tokens_source"] = _get_tokens_source( + sdk_tags, posthog_properties + ) ph_client.capture( distinct_id=posthog_distinct_id or posthog_trace_id, event="$ai_generation", - properties={ - **get_tags(), - **(posthog_properties or {}), - **(error_params or {}), - }, + properties=merged_properties, groups=posthog_groups, ) @@ -543,14 +570,19 @@ # send the event to posthog if hasattr(ph_client, "capture") and callable(ph_client.capture): + sdk_tags = get_tags() + merged_properties = { + **sdk_tags, + **(posthog_properties or {}), + **(error_params or {}), + } + merged_properties["$ai_tokens_source"] = _get_tokens_source( + sdk_tags, posthog_properties + ) ph_client.capture( distinct_id=posthog_distinct_id or posthog_trace_id, event="$ai_generation", - properties={ - **get_tags(), - **(posthog_properties or {}), - **(error_params or {}), - }, + properties=merged_properties, groups=posthog_groups, ) @@ -627,6 +659,15 @@ **(event_data.get("properties") or {}), } + # Determine token source: SDK-computed vs externally overridden + sdk_token_tags = { + "$ai_input_tokens": event_data["usage_stats"].get("input_tokens", 0), + "$ai_output_tokens": event_data["usage_stats"].get("output_tokens", 0), + } + event_properties["$ai_tokens_source"] = _get_tokens_source( + sdk_token_tags, event_data.get("properties") + ) + # Extract and add tools based on provider available_tools = extract_available_tool_calls( event_data["provider"], diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/posthog-7.9.3/posthog/feature_flags.py new/posthog-7.9.6/posthog/feature_flags.py --- old/posthog-7.9.3/posthog/feature_flags.py 2026-02-18 23:18:08.000000000 +0100 +++ new/posthog-7.9.6/posthog/feature_flags.py 2026-03-02 22:28:23.000000000 +0100 @@ -18,6 +18,30 @@ NONE_VALUES_ALLOWED_OPERATORS = ["is_not"] +# All operators supported by match_property, grouped by category. +EQUALITY_OPERATORS = ("exact", "is_not", "is_set", "is_not_set") +STRING_OPERATORS = ("icontains", "not_icontains", "regex", "not_regex") +NUMERIC_OPERATORS = ("gt", "gte", "lt", "lte") +DATE_OPERATORS = ("is_date_before", "is_date_after") +SEMVER_COMPARISON_OPERATORS = ( + "semver_eq", + "semver_neq", + "semver_gt", + "semver_gte", + "semver_lt", + "semver_lte", +) +SEMVER_RANGE_OPERATORS = ("semver_tilde", "semver_caret", "semver_wildcard") +SEMVER_OPERATORS = SEMVER_COMPARISON_OPERATORS + SEMVER_RANGE_OPERATORS + +PROPERTY_OPERATORS = ( + EQUALITY_OPERATORS + + STRING_OPERATORS + + NUMERIC_OPERATORS + + DATE_OPERATORS + + SEMVER_OPERATORS +) + class InconclusiveMatchError(Exception): pass @@ -385,6 +409,9 @@ operator = property.get("operator") or "exact" value = property.get("value") + if operator not in PROPERTY_OPERATORS: + raise InconclusiveMatchError(f"Unknown operator {operator}") + if key not in property_values: raise InconclusiveMatchError( "can't match properties without a given property value" @@ -505,7 +532,64 @@ "The date provided must be a string or date object" ) - # if we get here, we don't know how to handle the operator + if operator in SEMVER_OPERATORS: + try: + override_parsed = parse_semver(override_value) + except (ValueError, TypeError): + raise InconclusiveMatchError( + f"Person property value '{override_value}' is not a valid semver" + ) + + if operator in SEMVER_COMPARISON_OPERATORS: + try: + flag_parsed = parse_semver(value) + except (ValueError, TypeError): + raise InconclusiveMatchError( + f"Flag semver value '{value}' is not a valid semver" + ) + + if operator == "semver_eq": + return override_parsed == flag_parsed + elif operator == "semver_neq": + return override_parsed != flag_parsed + elif operator == "semver_gt": + return override_parsed > flag_parsed + elif operator == "semver_gte": + return override_parsed >= flag_parsed + elif operator == "semver_lt": + return override_parsed < flag_parsed + elif operator == "semver_lte": + return override_parsed <= flag_parsed + + elif operator == "semver_tilde": + try: + lower, upper = _tilde_bounds(str(value)) + except (ValueError, TypeError): + raise InconclusiveMatchError( + f"Flag semver value '{value}' is not valid for tilde operator" + ) + return lower <= override_parsed < upper + + elif operator == "semver_caret": + try: + lower, upper = _caret_bounds(str(value)) + except (ValueError, TypeError): + raise InconclusiveMatchError( + f"Flag semver value '{value}' is not valid for caret operator" + ) + return lower <= override_parsed < upper + + elif operator == "semver_wildcard": + try: + lower, upper = _wildcard_bounds(str(value)) + except (ValueError, TypeError): + raise InconclusiveMatchError( + f"Flag semver value '{value}' is not valid for wildcard operator" + ) + return lower <= override_parsed < upper + + # Unreachable: all operators in PROPERTY_OPERATORS are handled above, + # and unknown operators are rejected at the top of this function. raise InconclusiveMatchError(f"Unknown operator {operator}") @@ -686,3 +770,75 @@ return parsed_dt else: return None + + +def parse_semver(value: str) -> tuple: + """Parse a semver string into a comparable (major, minor, patch) integer tuple. + + Matches the behavior of the sortableSemver HogQL function: + - Handles v-prefix, whitespace, pre-release suffixes + - Defaults missing components to 0 (e.g., 1.2 -> 1.2.0) + Raises ValueError if parsing fails. + """ + text = str(value).strip().lstrip("vV") + # Strip pre-release/build metadata suffix + text = text.split("-")[0].split("+")[0] + parts = text.split(".") + + if not parts or not parts[0]: + raise ValueError("Invalid semver format") + + major = int(parts[0]) + minor = int(parts[1]) if len(parts) > 1 and parts[1] else 0 + patch = int(parts[2]) if len(parts) > 2 and parts[2] else 0 + + return (major, minor, patch) + + +def _tilde_bounds(value: str) -> tuple: + """~1.2.3 means >=1.2.3 <1.3.0 (allows patch-level changes).""" + major, minor, patch = parse_semver(value) + return (major, minor, patch), (major, minor + 1, 0) + + +def _caret_bounds(value: str) -> tuple: + """Caret follows semver spec: + ^1.2.3 means >=1.2.3 <2.0.0 + ^0.2.3 means >=0.2.3 <0.3.0 + ^0.0.3 means >=0.0.3 <0.0.4 + """ + major, minor, patch = parse_semver(value) + lower = (major, minor, patch) + + if major > 0: + upper = (major + 1, 0, 0) + elif minor > 0: + upper = (0, minor + 1, 0) + else: + upper = (0, 0, patch + 1) + + return lower, upper + + +def _wildcard_bounds(value: str) -> tuple: + """Wildcard matching: + 1.* means >=1.0.0 <2.0.0 + 1.2.* means >=1.2.0 <1.3.0 + """ + cleaned = str(value).strip().lstrip("vV").replace("*", "").rstrip(".") + if not cleaned: + raise ValueError("Invalid wildcard pattern") + + parts = [p for p in cleaned.split(".") if p] + if not parts: + raise ValueError("Invalid wildcard pattern") + + if len(parts) == 1: + major = int(parts[0]) + return (major, 0, 0), (major + 1, 0, 0) + elif len(parts) == 2: + major, minor = int(parts[0]), int(parts[1]) + return (major, minor, 0), (major, minor + 1, 0) + else: + major, minor, patch = int(parts[0]), int(parts[1]), int(parts[2]) + return (major, minor, patch), (major, minor, patch + 1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/posthog-7.9.3/posthog/integrations/django.py new/posthog-7.9.6/posthog/integrations/django.py --- old/posthog-7.9.3/posthog/integrations/django.py 2026-02-18 23:18:08.000000000 +0100 +++ new/posthog-7.9.6/posthog/integrations/django.py 2026-03-02 22:28:23.000000000 +0100 @@ -155,7 +155,7 @@ # Extract IP address ip_address = request.headers.get("X-Forwarded-For") if ip_address: - tags["$ip_address"] = ip_address + tags["$ip"] = ip_address # Extract user agent user_agent = request.headers.get("User-Agent") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/posthog-7.9.3/posthog/test/ai/test_tokens_source.py new/posthog-7.9.6/posthog/test/ai/test_tokens_source.py --- old/posthog-7.9.3/posthog/test/ai/test_tokens_source.py 1970-01-01 01:00:00.000000000 +0100 +++ new/posthog-7.9.6/posthog/test/ai/test_tokens_source.py 2026-03-02 22:28:23.000000000 +0100 @@ -0,0 +1,62 @@ +from parameterized import parameterized + +from posthog.ai.utils import _get_tokens_source + + [email protected]( + [ + ("no_posthog_properties", {"$ai_input_tokens": 100}, None, "sdk"), + ("empty_posthog_properties", {"$ai_input_tokens": 100}, {}, "sdk"), + ( + "unrelated_posthog_properties", + {"$ai_input_tokens": 100}, + {"foo": "bar"}, + "sdk", + ), + ( + "override_input_tokens", + {"$ai_input_tokens": 100}, + {"$ai_input_tokens": 999}, + "passthrough", + ), + ( + "override_output_tokens", + {"$ai_output_tokens": 50}, + {"$ai_output_tokens": 999}, + "passthrough", + ), + ( + "override_total_tokens", + {"$ai_input_tokens": 100}, + {"$ai_total_tokens": 999}, + "passthrough", + ), + ( + "override_cache_read", + {"$ai_input_tokens": 100}, + {"$ai_cache_read_input_tokens": 500}, + "passthrough", + ), + ( + "override_cache_creation", + {"$ai_input_tokens": 100}, + {"$ai_cache_creation_input_tokens": 200}, + "passthrough", + ), + ( + "override_reasoning_tokens", + {"$ai_input_tokens": 100}, + {"$ai_reasoning_tokens": 300}, + "passthrough", + ), + ( + "mixed_override_and_custom", + {"$ai_input_tokens": 100}, + {"$ai_input_tokens": 999, "custom_key": "value"}, + "passthrough", + ), + ] +) +def test_get_tokens_source(name, sdk_tags, posthog_properties, expected): + result = _get_tokens_source(sdk_tags, posthog_properties) + assert result == expected diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/posthog-7.9.3/posthog/test/test_feature_flags.py new/posthog-7.9.6/posthog/test/test_feature_flags.py --- old/posthog-7.9.3/posthog/test/test_feature_flags.py 2026-02-18 23:18:08.000000000 +0100 +++ new/posthog-7.9.6/posthog/test/test_feature_flags.py 2026-03-02 22:28:23.000000000 +0100 @@ -4198,6 +4198,207 @@ with self.assertRaises(InconclusiveMatchError): self.assertFalse(match_property(property_k, {"key": "random"})) + def test_match_properties_semver_eq(self): + prop = self.property(key="version", value="1.2.3", operator="semver_eq") + self.assertTrue(match_property(prop, {"version": "1.2.3"})) + self.assertFalse(match_property(prop, {"version": "1.2.4"})) + self.assertFalse(match_property(prop, {"version": "1.2.2"})) + self.assertFalse(match_property(prop, {"version": "2.0.0"})) + + # Pre-release suffix is stripped for comparison + self.assertTrue(match_property(prop, {"version": "1.2.3-alpha.1"})) + + # Partial versions default missing parts to 0 + prop_partial = self.property(key="version", value="1.2", operator="semver_eq") + self.assertTrue(match_property(prop_partial, {"version": "1.2.0"})) + self.assertFalse(match_property(prop_partial, {"version": "1.2.1"})) + + def test_match_properties_semver_neq(self): + prop = self.property(key="version", value="1.2.3", operator="semver_neq") + self.assertFalse(match_property(prop, {"version": "1.2.3"})) + self.assertTrue(match_property(prop, {"version": "1.2.4"})) + self.assertTrue(match_property(prop, {"version": "2.0.0"})) + + def test_match_properties_semver_gt(self): + prop = self.property(key="version", value="1.2.3", operator="semver_gt") + self.assertTrue(match_property(prop, {"version": "1.2.4"})) + self.assertTrue(match_property(prop, {"version": "1.3.0"})) + self.assertTrue(match_property(prop, {"version": "2.0.0"})) + self.assertFalse(match_property(prop, {"version": "1.2.3"})) + self.assertFalse(match_property(prop, {"version": "1.2.2"})) + self.assertFalse(match_property(prop, {"version": "0.9.0"})) + + def test_match_properties_semver_gte(self): + prop = self.property(key="version", value="1.2.3", operator="semver_gte") + self.assertTrue(match_property(prop, {"version": "1.2.3"})) + self.assertTrue(match_property(prop, {"version": "1.2.4"})) + self.assertTrue(match_property(prop, {"version": "2.0.0"})) + self.assertFalse(match_property(prop, {"version": "1.2.2"})) + self.assertFalse(match_property(prop, {"version": "0.9.0"})) + + def test_match_properties_semver_lt(self): + prop = self.property(key="version", value="1.2.3", operator="semver_lt") + self.assertTrue(match_property(prop, {"version": "1.2.2"})) + self.assertTrue(match_property(prop, {"version": "1.1.0"})) + self.assertTrue(match_property(prop, {"version": "0.9.0"})) + self.assertFalse(match_property(prop, {"version": "1.2.3"})) + self.assertFalse(match_property(prop, {"version": "1.2.4"})) + self.assertFalse(match_property(prop, {"version": "2.0.0"})) + + def test_match_properties_semver_lte(self): + prop = self.property(key="version", value="1.2.3", operator="semver_lte") + self.assertTrue(match_property(prop, {"version": "1.2.3"})) + self.assertTrue(match_property(prop, {"version": "1.2.2"})) + self.assertTrue(match_property(prop, {"version": "0.9.0"})) + self.assertFalse(match_property(prop, {"version": "1.2.4"})) + self.assertFalse(match_property(prop, {"version": "2.0.0"})) + + def test_match_properties_semver_tilde(self): + # ~1.2.3 means >=1.2.3 <1.3.0 + prop = self.property(key="version", value="1.2.3", operator="semver_tilde") + self.assertTrue(match_property(prop, {"version": "1.2.3"})) + self.assertTrue(match_property(prop, {"version": "1.2.5"})) + self.assertTrue(match_property(prop, {"version": "1.2.99"})) + self.assertFalse(match_property(prop, {"version": "1.3.0"})) + self.assertFalse(match_property(prop, {"version": "1.2.2"})) + self.assertFalse(match_property(prop, {"version": "2.0.0"})) + + def test_match_properties_semver_caret(self): + # ^1.2.3 means >=1.2.3 <2.0.0 + prop = self.property(key="version", value="1.2.3", operator="semver_caret") + self.assertTrue(match_property(prop, {"version": "1.2.3"})) + self.assertTrue(match_property(prop, {"version": "1.9.0"})) + self.assertTrue(match_property(prop, {"version": "1.99.99"})) + self.assertFalse(match_property(prop, {"version": "2.0.0"})) + self.assertFalse(match_property(prop, {"version": "1.2.2"})) + self.assertFalse(match_property(prop, {"version": "0.9.0"})) + + # ^0.2.3 means >=0.2.3 <0.3.0 (leftmost non-zero is minor) + prop_zero_major = self.property( + key="version", value="0.2.3", operator="semver_caret" + ) + self.assertTrue(match_property(prop_zero_major, {"version": "0.2.3"})) + self.assertTrue(match_property(prop_zero_major, {"version": "0.2.9"})) + self.assertFalse(match_property(prop_zero_major, {"version": "0.3.0"})) + self.assertFalse(match_property(prop_zero_major, {"version": "1.0.0"})) + + # ^0.0.3 means >=0.0.3 <0.0.4 (leftmost non-zero is patch) + prop_zero_minor = self.property( + key="version", value="0.0.3", operator="semver_caret" + ) + self.assertTrue(match_property(prop_zero_minor, {"version": "0.0.3"})) + self.assertFalse(match_property(prop_zero_minor, {"version": "0.0.4"})) + self.assertFalse(match_property(prop_zero_minor, {"version": "0.1.0"})) + + def test_match_properties_semver_wildcard(self): + # 1.2.* means >=1.2.0 <1.3.0 + prop = self.property(key="version", value="1.2.*", operator="semver_wildcard") + self.assertTrue(match_property(prop, {"version": "1.2.0"})) + self.assertTrue(match_property(prop, {"version": "1.2.5"})) + self.assertTrue(match_property(prop, {"version": "1.2.99"})) + self.assertFalse(match_property(prop, {"version": "1.3.0"})) + self.assertFalse(match_property(prop, {"version": "1.1.9"})) + self.assertFalse(match_property(prop, {"version": "2.0.0"})) + + # 1.* means >=1.0.0 <2.0.0 + prop_major = self.property( + key="version", value="1.*", operator="semver_wildcard" + ) + self.assertTrue(match_property(prop_major, {"version": "1.0.0"})) + self.assertTrue(match_property(prop_major, {"version": "1.99.99"})) + self.assertFalse(match_property(prop_major, {"version": "2.0.0"})) + self.assertFalse(match_property(prop_major, {"version": "0.9.0"})) + + def test_match_properties_semver_with_prerelease(self): + # Pre-release suffixes are stripped before comparison + prop = self.property(key="version", value="1.2.3", operator="semver_gt") + self.assertTrue(match_property(prop, {"version": "1.3.0-beta.1"})) + self.assertFalse(match_property(prop, {"version": "1.2.2-rc.1"})) + + # Flag value can also have pre-release suffix + prop_pre = self.property( + key="version", value="1.2.3-alpha", operator="semver_gte" + ) + self.assertTrue(match_property(prop_pre, {"version": "1.2.3"})) + self.assertTrue(match_property(prop_pre, {"version": "2.0.0"})) + self.assertFalse(match_property(prop_pre, {"version": "1.2.2"})) + + def test_match_properties_semver_edge_cases(self): + """Test semver parsing handles v-prefix, whitespace, leading zeros, and other common formats.""" + prop = self.property(key="version", value="1.2.3", operator="semver_eq") + + # v-prefix: "v1.2.3" -> extracts "1.2.3" + self.assertTrue(match_property(prop, {"version": "v1.2.3"})) + + # Leading space: " 1.2.3" -> extracts "1.2.3" + self.assertTrue(match_property(prop, {"version": " 1.2.3"})) + + # Trailing space: "1.2.3 " -> extracts "1.2.3" + self.assertTrue(match_property(prop, {"version": "1.2.3 "})) + + # Leading zeros: "01.02.03" -> int("01")=1, int("02")=2, int("03")=3 + self.assertTrue(match_property(prop, {"version": "01.02.03"})) + + # Flag value with v-prefix + prop_v = self.property(key="version", value="v1.2.3", operator="semver_eq") + self.assertTrue(match_property(prop_v, {"version": "1.2.3"})) + + # 0.0.0 minimal version + prop_min = self.property(key="version", value="0.0.0", operator="semver_eq") + self.assertTrue(match_property(prop_min, {"version": "0.0.0"})) + + prop_gt_min = self.property(key="version", value="0.0.0", operator="semver_gt") + self.assertTrue(match_property(prop_gt_min, {"version": "0.0.1"})) + self.assertFalse(match_property(prop_gt_min, {"version": "0.0.0"})) + + # 4-part version: regex extracts "1.2.3.4" -> takes first 3 parts + prop_four = self.property(key="version", value="1.2.3", operator="semver_eq") + self.assertTrue(match_property(prop_four, {"version": "1.2.3.4"})) + + # Truly invalid values raise InconclusiveMatchError + with self.assertRaises(InconclusiveMatchError): + match_property(prop, {"version": "abc"}) + + with self.assertRaises(InconclusiveMatchError): + match_property(prop, {"version": ""}) + + # Leading dot: ".1.2.3" -> invalid, empty first component + with self.assertRaises(InconclusiveMatchError): + match_property(prop, {"version": ".1.2.3"}) + + # Caret with v-prefix in flag value + prop_caret_v = self.property( + key="version", value="v1.2.3", operator="semver_caret" + ) + self.assertTrue(match_property(prop_caret_v, {"version": "1.5.0"})) + self.assertFalse(match_property(prop_caret_v, {"version": "2.0.0"})) + + # Wildcard with v-prefix in property value + prop_wild = self.property( + key="version", value="1.2.*", operator="semver_wildcard" + ) + self.assertTrue(match_property(prop_wild, {"version": "v1.2.5"})) + self.assertFalse(match_property(prop_wild, {"version": "v1.3.0"})) + + def test_match_properties_semver_invalid_values(self): + prop = self.property(key="version", value="1.2.3", operator="semver_eq") + + # Invalid person property value + with self.assertRaises(InconclusiveMatchError): + match_property(prop, {"version": "not-a-version"}) + + # Missing key + with self.assertRaises(InconclusiveMatchError): + match_property(prop, {"other_key": "1.2.3"}) + + # None override value returns False (handled before semver logic) + self.assertFalse(match_property(prop, {"version": None})) + + # Invalid flag value + prop_bad = self.property(key="version", value="not-valid", operator="semver_gt") + with self.assertRaises(InconclusiveMatchError): + match_property(prop_bad, {"version": "1.2.3"}) + def test_unknown_operator(self): property_a = self.property(key="key", value="2022-05-01", operator="is_unknown") with self.assertRaises(InconclusiveMatchError) as exception_context: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/posthog-7.9.3/posthog/version.py new/posthog-7.9.6/posthog/version.py --- old/posthog-7.9.3/posthog/version.py 2026-02-18 23:20:09.000000000 +0100 +++ new/posthog-7.9.6/posthog/version.py 2026-03-02 22:28:45.000000000 +0100 @@ -1 +1 @@ -VERSION = "7.9.3" +VERSION = "7.9.6" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/posthog-7.9.3/posthog.egg-info/PKG-INFO new/posthog-7.9.6/posthog.egg-info/PKG-INFO --- old/posthog-7.9.3/posthog.egg-info/PKG-INFO 2026-02-18 23:20:11.000000000 +0100 +++ new/posthog-7.9.6/posthog.egg-info/PKG-INFO 2026-03-02 22:28:48.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: posthog -Version: 7.9.3 +Version: 7.9.6 Summary: Integrate PostHog into any python application. Home-page: https://github.com/posthog/posthog-python Author: Posthog diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/posthog-7.9.3/posthog.egg-info/SOURCES.txt new/posthog-7.9.6/posthog.egg-info/SOURCES.txt --- old/posthog-7.9.3/posthog.egg-info/SOURCES.txt 2026-02-18 23:20:11.000000000 +0100 +++ new/posthog-7.9.6/posthog.egg-info/SOURCES.txt 2026-03-02 22:28:48.000000000 +0100 @@ -67,5 +67,6 @@ posthog/test/ai/test_prompts.py posthog/test/ai/test_sanitization.py posthog/test/ai/test_system_prompts.py +posthog/test/ai/test_tokens_source.py posthog/test/ai/openai_agents/__init__.py posthog/test/ai/openai_agents/test_processor.py \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/posthog-7.9.3/pyproject.toml new/posthog-7.9.6/pyproject.toml --- old/posthog-7.9.3/pyproject.toml 2026-02-18 23:20:08.000000000 +0100 +++ new/posthog-7.9.6/pyproject.toml 2026-03-02 22:28:45.000000000 +0100 @@ -4,7 +4,7 @@ [project] name = "posthog" -version = "7.9.3" +version = "7.9.6" description = "Integrate PostHog into any python application." authors = [{ name = "PostHog", email = "[email protected]" }] maintainers = [{ name = "PostHog", email = "[email protected]" }]
